mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 09:17:46 +01:00
Merge branch 'beta' into bump-2023.2.0
This commit is contained in:
commit
f1f96f16e9
236 changed files with 9203 additions and 1103 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -52,6 +52,10 @@ jobs:
|
|||
file: tests/test6.yaml
|
||||
name: Test tests/test6.yaml
|
||||
pio_cache_key: test6
|
||||
- id: test
|
||||
file: tests/test7.yaml
|
||||
name: Test tests/test7.yaml
|
||||
pio_cache_key: test7
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
- id: clang-format
|
||||
|
|
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
|
@ -67,8 +67,10 @@ jobs:
|
|||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.image.title == 'lint' }}
|
||||
needs: [init]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- title: "ha-addon"
|
||||
|
@ -136,14 +138,18 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d '{"ref":"main","inputs":{"version":"${{ github.event.release.tag_name }}","content":${{ toJSON(github.event.release.body) }}}}'
|
||||
# yamllint enable rule:line-length
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "home-assistant-addon",
|
||||
workflow_id: "bump-version.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ github.event.release.tag_name }}",
|
||||
content: ${{ toJSON(github.event.release.body) }}
|
||||
}
|
||||
})
|
||||
|
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v6
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
|
@ -38,7 +38,7 @@ jobs:
|
|||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v6
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -128,3 +128,5 @@ tests/.esphome/
|
|||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
||||
.tests/
|
|
@ -3,7 +3,7 @@
|
|||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.10.0
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
|
20
CODEOWNERS
20
CODEOWNERS
|
@ -41,6 +41,8 @@ esphome/components/ble_client/* @buxtronix
|
|||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @MrEditor97
|
||||
|
@ -69,6 +71,7 @@ esphome/components/display_menu_base/* @numo68
|
|||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
|
@ -100,9 +103,11 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
|
|||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
|
@ -111,13 +116,17 @@ esphome/components/integration/* @OttoWinter
|
|||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
|
@ -138,6 +147,7 @@ esphome/components/mcp9808/* @k7hpn
|
|||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
|
@ -164,6 +174,8 @@ esphome/components/nfc/* @jesserockz
|
|||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
|
@ -204,8 +216,12 @@ esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
|||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sigma_delta_output/* @Cat-Ion
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm10bit_base/* @Cossid
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/sm2235/* @Cossid
|
||||
esphome/components/sm2335/* @Cossid
|
||||
esphome/components/sml/* @alengwenus
|
||||
esphome/components/smt100/* @piechade
|
||||
esphome/components/sn74hc165/* @jesserockz
|
||||
|
@ -234,6 +250,7 @@ esphome/components/switch/* @esphome/core
|
|||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
|
@ -258,12 +275,15 @@ esphome/components/uart/* @esphome/core
|
|||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/whynter/* @aeonsablaze
|
||||
esphome/components/wiegand/* @ssieb
|
||||
esphome/components/wl_134/* @hobbypunk90
|
||||
esphome/components/x9c/* @EtienneMD
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
|
|
|
@ -26,7 +26,7 @@ RUN \
|
|||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+deb11u3 \
|
||||
curl=7.74.0-1.3+deb11u5 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
|
|
|
@ -8,32 +8,49 @@ import re
|
|||
import sys
|
||||
|
||||
|
||||
CHANNEL_DEV = 'dev'
|
||||
CHANNEL_BETA = 'beta'
|
||||
CHANNEL_RELEASE = 'release'
|
||||
CHANNEL_DEV = "dev"
|
||||
CHANNEL_BETA = "beta"
|
||||
CHANNEL_RELEASE = "release"
|
||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
|
||||
|
||||
ARCH_AMD64 = 'amd64'
|
||||
ARCH_ARMV7 = 'armv7'
|
||||
ARCH_AARCH64 = 'aarch64'
|
||||
ARCH_AMD64 = "amd64"
|
||||
ARCH_ARMV7 = "armv7"
|
||||
ARCH_AARCH64 = "aarch64"
|
||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
|
||||
|
||||
TYPE_DOCKER = 'docker'
|
||||
TYPE_HA_ADDON = 'ha-addon'
|
||||
TYPE_LINT = 'lint'
|
||||
TYPE_DOCKER = "docker"
|
||||
TYPE_HA_ADDON = "ha-addon"
|
||||
TYPE_LINT = "lint"
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
|
||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
|
||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
|
||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||
parser.add_argument(
|
||||
"--tag",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arch", choices=ARCHS, required=False, help="The architecture to build for"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-type", choices=TYPES, required=True, help="The type of build to run"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Don't run any commands, just print them"
|
||||
)
|
||||
subparsers = parser.add_subparsers(
|
||||
help="Action to perform", dest="command", required=True
|
||||
)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
|
||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||
build_parser.add_argument(
|
||||
"--load", help="Load the docker image locally", action="store_true"
|
||||
)
|
||||
manifest_parser = subparsers.add_parser(
|
||||
"manifest", help="Create a manifest from already pushed images"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
@ -49,7 +66,7 @@ class DockerParams:
|
|||
prefix = {
|
||||
TYPE_DOCKER: "esphome/esphome",
|
||||
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
||||
TYPE_LINT: "esphome/esphome-lint"
|
||||
TYPE_LINT: "esphome/esphome-lint",
|
||||
}[build_type]
|
||||
build_to = f"{prefix}-{arch}"
|
||||
baseimgtype = {
|
||||
|
@ -128,13 +145,21 @@ def main():
|
|||
|
||||
# 3. build
|
||||
cmd = [
|
||||
"docker", "buildx", "build",
|
||||
"--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg", f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from", f"type=registry,ref={cache_img}",
|
||||
"--file", "docker/Dockerfile",
|
||||
"--platform", params.platform,
|
||||
"--target", params.target,
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--build-arg",
|
||||
f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg",
|
||||
f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from",
|
||||
f"type=registry,ref={cache_img}",
|
||||
"--file",
|
||||
"docker/Dockerfile",
|
||||
"--platform",
|
||||
params.platform,
|
||||
"--target",
|
||||
params.target,
|
||||
]
|
||||
for img in imgs:
|
||||
cmd += ["--tag", img]
|
||||
|
@ -160,9 +185,7 @@ def main():
|
|||
run_command(*cmd)
|
||||
# 2. Push manifests
|
||||
for target in targets:
|
||||
run_command(
|
||||
"docker", "manifest", "push", target
|
||||
)
|
||||
run_command("docker", "manifest", "push", target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -339,7 +339,7 @@ def command_config(args, config):
|
|||
_LOGGER.info("Configuration is valid!")
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
safe_print(yaml_util.dump(config, args.show_secrets))
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -665,6 +665,9 @@ def parse_args(argv):
|
|||
parser_config.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_config.add_argument(
|
||||
"--show-secrets", help="Show secrets in output.", action="store_true"
|
||||
)
|
||||
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
|
|
|
@ -9,7 +9,7 @@ static const char *const TAG = "ads1115";
|
|||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111; // 3300_SPS for ADS1015
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
|
@ -18,6 +18,9 @@ void ADS1115Component::setup() {
|
|||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
|
||||
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
|
@ -77,6 +80,7 @@ void ADS1115Component::dump_config() {
|
|||
LOG_SENSOR(" ", "Sensor", sensor);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", sensor->get_multiplexer());
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
|
||||
}
|
||||
}
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
|
@ -127,27 +131,45 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
|||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
|
||||
if (sensor->get_resolution() == ADS1015_12_BITS) {
|
||||
bool negative = (raw_conversion >> 15) == 1;
|
||||
|
||||
// shift raw_conversion as it's only 12-bits, left justified
|
||||
raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
|
||||
|
||||
// check if number was negative in order to keep the sign
|
||||
if (negative) {
|
||||
// the number was negative
|
||||
// 1) set the negative bit back
|
||||
raw_conversion |= 0x8000;
|
||||
// 2) reset the former (shifted) negative bit
|
||||
raw_conversion &= 0xF7FF;
|
||||
}
|
||||
}
|
||||
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
|
||||
float millivolts;
|
||||
float divider = (sensor->get_resolution() == ADS1115_16_BITS) ? 32768.0f : 2048.0f;
|
||||
switch (sensor->get_gain()) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = signed_conversion * 0.187500f;
|
||||
millivolts = (signed_conversion * 6144) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = signed_conversion * 0.125000f;
|
||||
millivolts = (signed_conversion * 4096) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = signed_conversion * 0.062500f;
|
||||
millivolts = (signed_conversion * 2048) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = signed_conversion * 0.031250f;
|
||||
millivolts = (signed_conversion * 1024) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = signed_conversion * 0.015625f;
|
||||
millivolts = (signed_conversion * 512) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = signed_conversion * 0.007813f;
|
||||
millivolts = (signed_conversion * 256) / divider;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
|
|
|
@ -30,6 +30,11 @@ enum ADS1115Gain {
|
|||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
enum ADS1115Resolution {
|
||||
ADS1115_16_BITS = 16,
|
||||
ADS1015_12_BITS = 12,
|
||||
};
|
||||
|
||||
class ADS1115Sensor;
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
|
@ -58,15 +63,17 @@ class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public vol
|
|||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||
|
||||
void set_resolution(ADS1115Resolution resolution) { resolution_ = resolution; }
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||
uint8_t get_gain() const { return gain_; }
|
||||
uint8_t get_resolution() const { return resolution_; }
|
||||
|
||||
protected:
|
||||
ADS1115Component *parent_;
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
ADS1115Resolution resolution_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import sensor, voltage_sampler
|
|||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_MULTIPLEXER,
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
|
@ -35,6 +36,12 @@ GAIN = {
|
|||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
ADS1115Resolution = ads1115_ns.enum("ADS1115Resolution")
|
||||
RESOLUTION = {
|
||||
"16_BITS": ADS1115Resolution.ADS1115_16_BITS,
|
||||
"12_BITS": ADS1115Resolution.ADS1015_12_BITS,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
|
@ -63,6 +70,9 @@ CONFIG_SCHEMA = (
|
|||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -77,5 +87,6 @@ async def to_code(config):
|
|||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(paren.register_sensor(var))
|
||||
|
|
|
@ -117,7 +117,7 @@ async def to_code(config):
|
|||
data[pos] = rgb & 255
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == "BINARY":
|
||||
elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
|
|
|
@ -23,7 +23,7 @@ void APDS9960::setup() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (id != 0xAB && id != 0x9C) { // APDS9960 all should have one of these IDs
|
||||
if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
|
||||
this->error_code_ = WRONG_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
|
|
@ -206,6 +206,8 @@ message DeviceInfoResponse {
|
|||
uint32 bluetooth_proxy_version = 11;
|
||||
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
@ -785,6 +787,7 @@ enum ClimateFanMode {
|
|||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
CLIMATE_FAN_QUIET = 9;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
|
|
|
@ -930,6 +930,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.friendly_name = App.get_friendly_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
|
|
|
@ -616,6 +616,9 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
|||
struct iovec iov[2];
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
|
@ -913,6 +916,9 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
|
|||
struct iovec iov[2];
|
||||
iov[0].iov_base = &header[0];
|
||||
iov[0].iov_len = header.size();
|
||||
if (payload_len == 0) {
|
||||
return write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
|
||||
|
|
|
@ -235,6 +235,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
|
|||
return "CLIMATE_FAN_FOCUS";
|
||||
case enums::CLIMATE_FAN_DIFFUSE:
|
||||
return "CLIMATE_FAN_DIFFUSE";
|
||||
case enums::CLIMATE_FAN_QUIET:
|
||||
return "CLIMATE_FAN_QUIET";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
@ -628,6 +630,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
|||
this->manufacturer = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->friendly_name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -645,6 +651,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_uint32(10, this->webserver_port);
|
||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
|
@ -699,6 +706,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
|||
out.append(" manufacturer: ");
|
||||
out.append("'").append(this->manufacturer).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" friendly_name: ");
|
||||
out.append("'").append(this->friendly_name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -99,6 +99,7 @@ enum ClimateFanMode : uint32_t {
|
|||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
|
@ -276,6 +277,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||
uint32_t webserver_port{0};
|
||||
uint32_t bluetooth_proxy_version{0};
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
|
|
@ -37,9 +37,13 @@ void BedJetFan::control(const fan::FanCall &call) {
|
|||
|
||||
// ignore speed changes if not on or turning on
|
||||
if (this->state && call.get_speed().has_value()) {
|
||||
this->speed = *call.get_speed();
|
||||
this->parent_->set_fan_index(this->speed);
|
||||
did_change = true;
|
||||
auto speed = *call.get_speed();
|
||||
if (speed >= 1) {
|
||||
this->speed = speed;
|
||||
// Fan.speed is 1-20, but Bedjet expects 0-19, so subtract 1
|
||||
this->parent_->set_fan_index(this->speed - 1);
|
||||
did_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (did_change) {
|
||||
|
@ -57,8 +61,9 @@ void BedJetFan::on_status(const BedjetStatusPacket *data) {
|
|||
did_change = true;
|
||||
}
|
||||
|
||||
if (data->fan_step != this->speed) {
|
||||
this->speed = data->fan_step;
|
||||
// BedjetStatusPacket.fan_step is in range 0-19, but Fan.speed wants 1-20.
|
||||
if (data->fan_step + 1 != this->speed) {
|
||||
this->speed = data->fan_step + 1;
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
|
|
44
esphome/components/bp1658cj/__init__.py
Normal file
44
esphome/components/bp1658cj/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Cossid"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_MAX_POWER_COLOR_CHANNELS = "max_power_color_channels"
|
||||
CONF_MAX_POWER_WHITE_CHANNELS = "max_power_white_channels"
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
bp1658cj_ns = cg.esphome_ns.namespace("bp1658cj")
|
||||
BP1658CJ = bp1658cj_ns.class_("BP1658CJ", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BP1658CJ),
|
||||
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_MAX_POWER_COLOR_CHANNELS, default=2): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
cv.Optional(CONF_MAX_POWER_WHITE_CHANNELS, default=4): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
|
||||
cg.add(var.set_data_pin(data))
|
||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||
cg.add(var.set_clock_pin(clock))
|
||||
cg.add(var.set_max_power_color_channels(config[CONF_MAX_POWER_COLOR_CHANNELS]))
|
||||
cg.add(var.set_max_power_white_channels(config[CONF_MAX_POWER_WHITE_CHANNELS]))
|
110
esphome/components/bp1658cj/bp1658cj.cpp
Normal file
110
esphome/components/bp1658cj/bp1658cj.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "bp1658cj.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bp1658cj {
|
||||
|
||||
static const char *const TAG = "bp1658cj";
|
||||
|
||||
static const uint8_t BP1658CJ_MODEL_ID = 0x80;
|
||||
static const uint8_t BP1658CJ_ADDR_STANDBY = 0x0;
|
||||
static const uint8_t BP1658CJ_ADDR_START_3CH = 0x10;
|
||||
static const uint8_t BP1658CJ_ADDR_START_2CH = 0x20;
|
||||
static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30;
|
||||
|
||||
void BP1658CJ::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BP1658CJ Output Component...");
|
||||
this->data_pin_->setup();
|
||||
this->data_pin_->digital_write(false);
|
||||
this->clock_pin_->setup();
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->pwm_amounts_.resize(5, 0);
|
||||
}
|
||||
void BP1658CJ::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BP1658CJ:");
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Color Channels Max Power: %u", this->max_power_color_channels_);
|
||||
ESP_LOGCONFIG(TAG, " White Channels Max Power: %u", this->max_power_white_channels_);
|
||||
}
|
||||
|
||||
void BP1658CJ::loop() {
|
||||
if (!this->update_)
|
||||
return;
|
||||
|
||||
uint8_t data[12];
|
||||
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Off / Sleep
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_STANDBY;
|
||||
for (int i = 1; i < 12; i++)
|
||||
data[i] = 0;
|
||||
this->write_buffer_(data, 12);
|
||||
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
|
||||
// Only data on white channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_2CH;
|
||||
data[1] = 0 << 4 | this->max_power_white_channels_;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
} else if ((this->pwm_amounts_[0] > 0 || this->pwm_amounts_[1] > 0 || this->pwm_amounts_[2] > 0) &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Only data on RGB channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_3CH;
|
||||
data[1] = this->max_power_color_channels_ << 4 | 0;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
} else {
|
||||
// All channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_5CH;
|
||||
data[1] = this->max_power_color_channels_ << 4 | this->max_power_white_channels_;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
}
|
||||
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
}
|
||||
this->pwm_amounts_[channel] = value;
|
||||
}
|
||||
void BP1658CJ::write_bit_(bool value) {
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(true);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||
this->data_pin_->digital_write(false);
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
this->write_byte_(buffer[i]);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->clock_pin_->digital_write(true);
|
||||
this->data_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
} // namespace bp1658cj
|
||||
} // namespace esphome
|
64
esphome/components/bp1658cj/bp1658cj.h
Normal file
64
esphome/components/bp1658cj/bp1658cj.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace bp1658cj {
|
||||
|
||||
class BP1658CJ : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
void set_max_power_color_channels(uint8_t max_power_color_channels) {
|
||||
max_power_color_channels_ = max_power_color_channels;
|
||||
}
|
||||
void set_max_power_white_channels(uint8_t max_power_white_channels) {
|
||||
max_power_white_channels_ = max_power_white_channels;
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP1658CJ *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
this->parent_->set_channel_value_(this->channel_, amount);
|
||||
}
|
||||
|
||||
BP1658CJ *parent_;
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
protected:
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
GPIOPin *data_pin_;
|
||||
GPIOPin *clock_pin_;
|
||||
uint8_t max_power_color_channels_{4};
|
||||
uint8_t max_power_white_channels_{6};
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint16_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
};
|
||||
|
||||
} // namespace bp1658cj
|
||||
} // namespace esphome
|
27
esphome/components/bp1658cj/output.py
Normal file
27
esphome/components/bp1658cj/output.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from . import BP1658CJ
|
||||
|
||||
DEPENDENCIES = ["bp1658cj"]
|
||||
|
||||
Channel = BP1658CJ.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP1658CJ_ID = "bp1658cj_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP1658CJ_ID): cv.use_id(BP1658CJ),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_BP1658CJ_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
33
esphome/components/bp5758d/__init__.py
Normal file
33
esphome/components/bp5758d/__init__.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Cossid"]
|
||||
MULTI_CONF = True
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
bp5758d_ns = cg.esphome_ns.namespace("bp5758d")
|
||||
BP5758D = bp5758d_ns.class_("BP5758D", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BP5758D),
|
||||
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
|
||||
cg.add(var.set_data_pin(data))
|
||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||
cg.add(var.set_clock_pin(clock))
|
147
esphome/components/bp5758d/bp5758d.cpp
Normal file
147
esphome/components/bp5758d/bp5758d.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include "bp5758d.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bp5758d {
|
||||
|
||||
static const char *const TAG = "bp5758d";
|
||||
|
||||
static const uint8_t BP5758D_MODEL_ID = 0b10000000;
|
||||
static const uint8_t BP5758D_ADDR_STANDBY = 0b00000000;
|
||||
// Note, channel start address seems ambiguous and mis-translated.
|
||||
// Documentation states all are "invalid sleep"
|
||||
// All 3 values appear to activate all 5 channels. Using the mapping
|
||||
// from BP1658CJ ordering since it won't break anything.
|
||||
static const uint8_t BP5758D_ADDR_START_3CH = 0b00010000;
|
||||
static const uint8_t BP5758D_ADDR_START_2CH = 0b00100000;
|
||||
static const uint8_t BP5758D_ADDR_START_5CH = 0b00110000;
|
||||
static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111;
|
||||
|
||||
void BP5758D::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BP5758D Output Component...");
|
||||
this->data_pin_->setup();
|
||||
this->data_pin_->digital_write(false);
|
||||
this->clock_pin_->setup();
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->channel_current_.resize(5, 0);
|
||||
this->pwm_amounts_.resize(5, 0);
|
||||
}
|
||||
void BP5758D::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BP5758D:");
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
}
|
||||
|
||||
void BP5758D::loop() {
|
||||
if (!this->update_)
|
||||
return;
|
||||
|
||||
uint8_t data[17];
|
||||
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Off / Sleep
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY;
|
||||
for (int i = 1; i < 16; i++)
|
||||
data[i] = 0;
|
||||
this->write_buffer_(data, 17);
|
||||
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
|
||||
// Only data on white channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_2CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = 0;
|
||||
data[3] = 0;
|
||||
data[4] = 0;
|
||||
data[5] = this->pwm_amounts_[3] > 0 ? correct_current_level_bits_(this->channel_current_[3]) : 0;
|
||||
data[6] = this->pwm_amounts_[4] > 0 ? correct_current_level_bits_(this->channel_current_[4]) : 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
} else if ((this->pwm_amounts_[0] > 0 || this->pwm_amounts_[1] > 0 || this->pwm_amounts_[2] > 0) &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Only data on RGB channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_3CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = this->pwm_amounts_[0] > 0 ? correct_current_level_bits_(this->channel_current_[0]) : 0;
|
||||
data[3] = this->pwm_amounts_[1] > 0 ? correct_current_level_bits_(this->channel_current_[1]) : 0;
|
||||
data[4] = this->pwm_amounts_[2] > 0 ? correct_current_level_bits_(this->channel_current_[2]) : 0;
|
||||
data[5] = 0;
|
||||
data[6] = 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
} else {
|
||||
// All channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_5CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = this->pwm_amounts_[0] > 0 ? correct_current_level_bits_(this->channel_current_[0]) : 0;
|
||||
data[3] = this->pwm_amounts_[1] > 0 ? correct_current_level_bits_(this->channel_current_[1]) : 0;
|
||||
data[4] = this->pwm_amounts_[2] > 0 ? correct_current_level_bits_(this->channel_current_[2]) : 0;
|
||||
data[5] = this->pwm_amounts_[3] > 0 ? correct_current_level_bits_(this->channel_current_[3]) : 0;
|
||||
data[6] = this->pwm_amounts_[4] > 0 ? correct_current_level_bits_(this->channel_current_[4]) : 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
}
|
||||
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
uint8_t BP5758D::correct_current_level_bits_(uint8_t current) {
|
||||
// Anything below 64 uses normal bitmapping.
|
||||
if (current < 64) {
|
||||
return current;
|
||||
}
|
||||
|
||||
// Anything above 63 needs to be offset by +34 because the driver remaps bit 7 (normally 64) to 30.
|
||||
// (no idea why(!) but it is documented)
|
||||
// Example:
|
||||
// integer 64 would normally put out 0b01000000 but here 0b01000000 = 30 whereas everything lower
|
||||
// is normal, so we add 34 to the integer where
|
||||
// integer 98 = 0b01100010 which is 30 (7th bit adjusted) + 34 (1st-6th bits).
|
||||
return current + 34;
|
||||
}
|
||||
|
||||
void BP5758D::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
}
|
||||
this->pwm_amounts_[channel] = value;
|
||||
}
|
||||
|
||||
void BP5758D::set_channel_current_(uint8_t channel, uint8_t current) { this->channel_current_[channel] = current; }
|
||||
|
||||
void BP5758D::write_bit_(bool value) {
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP5758D::write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(true);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP5758D::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||
this->data_pin_->digital_write(false);
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
this->write_byte_(buffer[i]);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->clock_pin_->digital_write(true);
|
||||
this->data_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
} // namespace bp5758d
|
||||
} // namespace esphome
|
64
esphome/components/bp5758d/bp5758d.h
Normal file
64
esphome/components/bp5758d/bp5758d.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace bp5758d {
|
||||
|
||||
class BP5758D : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP5758D *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
void set_current(uint8_t current) { current_ = current; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
// We're enforcing channels start at 1 to mach OUT1-OUT5, we must adjust
|
||||
// to our 0-based array internally here by subtracting 1.
|
||||
this->parent_->set_channel_value_(this->channel_ - 1, amount);
|
||||
this->parent_->set_channel_current_(this->channel_ - 1, this->current_);
|
||||
}
|
||||
|
||||
BP5758D *parent_;
|
||||
uint8_t channel_;
|
||||
uint8_t current_;
|
||||
};
|
||||
|
||||
protected:
|
||||
uint8_t correct_current_level_bits_(uint8_t current);
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void set_channel_current_(uint8_t channel, uint8_t current);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
GPIOPin *data_pin_;
|
||||
GPIOPin *clock_pin_;
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint8_t> channel_current_;
|
||||
std::vector<uint16_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
};
|
||||
|
||||
} // namespace bp5758d
|
||||
} // namespace esphome
|
29
esphome/components/bp5758d/output.py
Normal file
29
esphome/components/bp5758d/output.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_CURRENT
|
||||
from . import BP5758D
|
||||
|
||||
DEPENDENCIES = ["bp5758d"]
|
||||
|
||||
Channel = BP5758D.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP5758D_ID = "bp5758d_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP5758D_ID): cv.use_id(BP5758D),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=5),
|
||||
cv.Optional(CONF_CURRENT, default=10): cv.int_range(min=0, max=90),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_BP5758D_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_current(config[CONF_CURRENT]))
|
|
@ -22,6 +22,8 @@ from esphome.const import (
|
|||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_STATE,
|
||||
CONF_PRESET,
|
||||
CONF_PRESET_COMMAND_TOPIC,
|
||||
CONF_PRESET_STATE_TOPIC,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
|
@ -73,6 +75,7 @@ CLIMATE_FAN_MODES = {
|
|||
"MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
"FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
"DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
"QUIET": ClimateFanMode.CLIMATE_FAN_QUIET,
|
||||
}
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
|
@ -142,6 +145,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
|||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
|
@ -216,7 +225,12 @@ async def setup_climate_core_(var, config):
|
|||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_PRESET_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_preset_command_topic(config[CONF_PRESET_COMMAND_TOPIC])
|
||||
)
|
||||
if CONF_PRESET_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_preset_state_topic(config[CONF_PRESET_STATE_TOPIC]))
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
|
|
|
@ -174,6 +174,8 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
|||
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "QUIET")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_QUIET);
|
||||
} else {
|
||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
|
|
|
@ -62,6 +62,8 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
|||
return LOG_STR("FOCUS");
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return LOG_STR("DIFFUSE");
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
return LOG_STR("QUIET");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ enum ClimateFanMode : uint8_t {
|
|||
CLIMATE_FAN_FOCUS = 7,
|
||||
/// The fan mode is set to Diffuse
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
/// The fan mode is set to Quiet
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace climate {
|
|||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
||||
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||
* - off, both, vertical, horizontal
|
||||
*
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import base64
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_WIFI, CONF_REF
|
||||
from esphome.const import CONF_REF, CONF_WIFI
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
from esphome import git
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
||||
|
@ -66,7 +69,13 @@ async def to_code(config):
|
|||
|
||||
|
||||
def import_config(
|
||||
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
|
||||
path: str,
|
||||
name: str,
|
||||
friendly_name: Optional[str],
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
encryption: bool = False,
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
|
@ -74,14 +83,21 @@ def import_config(
|
|||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": "ESP32" if "esp32" in import_url else "ESP8266",
|
||||
"board": "esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
|
||||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
ssid="!secret wifi_ssid",
|
||||
psk="!secret wifi_password",
|
||||
),
|
||||
wizard_file(**kwargs),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
|
@ -98,14 +114,21 @@ def import_config(
|
|||
p.write_text(req.text, encoding="utf8")
|
||||
|
||||
else:
|
||||
substitutions = {"name": name}
|
||||
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
|
||||
if friendly_name:
|
||||
substitutions["friendly_name"] = friendly_name
|
||||
esphome_core["friendly_name"] = "${friendly_name}"
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"substitutions": substitutions,
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {
|
||||
"name": "${name}",
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
|
|
|
@ -111,6 +111,7 @@ class DemoClimate : public climate::Climate, public Component {
|
|||
climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_FOCUS,
|
||||
climate::CLIMATE_FAN_DIFFUSE,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
|
|
|
@ -15,6 +15,84 @@ static const char *const TAG = "display";
|
|||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
||||
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
||||
this->x = this->x - horizontal;
|
||||
this->y = this->y - vertical;
|
||||
this->w = this->w + (2 * horizontal);
|
||||
this->h = this->h + (2 * vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::extend(Rect rect) {
|
||||
if (!this->is_set()) {
|
||||
this->x = rect.x;
|
||||
this->y = rect.y;
|
||||
this->w = rect.w;
|
||||
this->h = rect.h;
|
||||
} else {
|
||||
if (this->x > rect.x) {
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y > rect.y) {
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() < rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() < rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Rect::shrink(Rect rect) {
|
||||
if (!this->inside(rect)) {
|
||||
(*this) = Rect();
|
||||
} else {
|
||||
if (this->x < rect.x) {
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y < rect.y) {
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() > rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() > rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::inside(int16_t x, int16_t y, bool absolute) { // NOLINT
|
||||
if (!this->is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h));
|
||||
} else {
|
||||
return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2()));
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::inside(Rect rect, bool absolute) {
|
||||
if (!this->is_set() || !rect.is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
||||
} else {
|
||||
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::info(const std::string &prefix) {
|
||||
if (this->is_set()) {
|
||||
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h);
|
||||
} else
|
||||
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
||||
}
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buffer_ = allocator.allocate(buffer_length);
|
||||
|
@ -24,6 +102,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
|||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
||||
int DisplayBuffer::get_width() {
|
||||
|
@ -50,6 +129,9 @@ int DisplayBuffer::get_height() {
|
|||
}
|
||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return; // NOLINT
|
||||
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
|
@ -368,6 +450,10 @@ void DisplayBuffer::do_update_() {
|
|||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
}
|
||||
// remove all not ended clipping regions
|
||||
while (is_clipping()) {
|
||||
end_clipping();
|
||||
}
|
||||
}
|
||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
|
||||
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
|
||||
|
@ -392,6 +478,41 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time:
|
|||
}
|
||||
#endif
|
||||
|
||||
void DisplayBuffer::start_clipping(Rect rect) {
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
Rect r = this->clipping_rectangle_.back();
|
||||
rect.shrink(r);
|
||||
}
|
||||
this->clipping_rectangle_.push_back(rect);
|
||||
}
|
||||
void DisplayBuffer::end_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "clear: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.pop_back();
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::extend_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().extend(add_rect);
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::shrink_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().shrink(add_rect);
|
||||
}
|
||||
}
|
||||
Rect DisplayBuffer::get_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
return Rect();
|
||||
} else {
|
||||
return this->clipping_rectangle_.back();
|
||||
}
|
||||
}
|
||||
bool Glyph::get_pixel(int x, int y) const {
|
||||
const int x_data = x - this->glyph_data_->offset_x;
|
||||
const int y_data = y - this->glyph_data_->offset_y;
|
||||
|
@ -452,7 +573,7 @@ int Font::match_next_glyph(const char *str, int *match_length) {
|
|||
}
|
||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
||||
*baseline = this->baseline_;
|
||||
*height = this->bottom_;
|
||||
*height = this->height_;
|
||||
int i = 0;
|
||||
int min_x = 0;
|
||||
bool has_char = false;
|
||||
|
@ -483,7 +604,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
|||
*width = x - min_x;
|
||||
}
|
||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int bottom) : baseline_(baseline), bottom_(bottom) {
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(data + i);
|
||||
}
|
||||
|
@ -527,6 +648,7 @@ int Image::get_height() const { return this->height_; }
|
|||
ImageType Image::get_type() const { return this->type_; }
|
||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
||||
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
||||
int Image::get_current_frame() const { return 0; }
|
||||
|
||||
bool Animation::get_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
|
@ -100,6 +99,32 @@ enum DisplayRotation {
|
|||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
static const int16_t VALUE_NO_SET = 32766;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
int16_t x; ///< X coordinate of corner
|
||||
int16_t y; ///< Y coordinate of corner
|
||||
int16_t w; ///< Width of region
|
||||
int16_t h; ///< Height of region
|
||||
|
||||
Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT
|
||||
inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
|
||||
inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner
|
||||
inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner
|
||||
|
||||
inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
|
||||
|
||||
void expand(int16_t horizontal, int16_t vertical);
|
||||
|
||||
void extend(Rect rect);
|
||||
void shrink(Rect rect);
|
||||
|
||||
bool inside(Rect rect, bool absolute = false);
|
||||
bool inside(int16_t x, int16_t y, bool absolute = false);
|
||||
void info(const std::string &prefix = "rect info:");
|
||||
};
|
||||
|
||||
class Font;
|
||||
class Image;
|
||||
class DisplayBuffer;
|
||||
|
@ -126,6 +151,7 @@ class DisplayBuffer {
|
|||
int get_width();
|
||||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height();
|
||||
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
|
||||
|
||||
|
@ -374,6 +400,49 @@ class DisplayBuffer {
|
|||
*/
|
||||
virtual DisplayType get_display_type() = 0;
|
||||
|
||||
/** Set the clipping rectangle for further drawing
|
||||
*
|
||||
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
|
||||
*
|
||||
* return true if success, false if error
|
||||
*/
|
||||
void start_clipping(Rect rect);
|
||||
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
start_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Add a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void extend_clipping(Rect rect);
|
||||
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
this->extend_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** substract a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void shrink_clipping(Rect rect);
|
||||
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Reset the invalidation region
|
||||
*/
|
||||
void end_clipping();
|
||||
|
||||
/** Get the current the clipping rectangle
|
||||
*
|
||||
* return rect for active clipping region
|
||||
*/
|
||||
Rect get_clipping();
|
||||
|
||||
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
|
@ -390,6 +459,7 @@ class DisplayBuffer {
|
|||
DisplayPage *previous_page_{nullptr};
|
||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||
bool auto_clear_enabled_{true};
|
||||
std::vector<Rect> clipping_rectangle_;
|
||||
};
|
||||
|
||||
class DisplayPage {
|
||||
|
@ -448,18 +518,20 @@ class Font {
|
|||
* @param baseline The y-offset from the top of the text to the baseline.
|
||||
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
|
||||
*/
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int bottom);
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height);
|
||||
|
||||
int match_next_glyph(const char *str, int *match_length);
|
||||
|
||||
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
inline int get_height() { return this->height_; }
|
||||
|
||||
const std::vector<Glyph> &get_glyphs() const;
|
||||
|
||||
protected:
|
||||
std::vector<Glyph> glyphs_;
|
||||
int baseline_;
|
||||
int bottom_;
|
||||
int height_;
|
||||
};
|
||||
|
||||
class Image {
|
||||
|
@ -473,6 +545,8 @@ class Image {
|
|||
int get_height() const;
|
||||
ImageType get_type() const;
|
||||
|
||||
virtual int get_current_frame() const;
|
||||
|
||||
protected:
|
||||
int width_;
|
||||
int height_;
|
||||
|
@ -489,7 +563,7 @@ class Animation : public Image {
|
|||
Color get_grayscale_pixel(int x, int y) const override;
|
||||
|
||||
int get_animation_frame_count() const;
|
||||
int get_current_frame() const;
|
||||
int get_current_frame() const override;
|
||||
void next_frame();
|
||||
void prev_frame();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
|
@ -32,7 +33,6 @@ CONF_BACK = "back"
|
|||
CONF_TEXT = "text"
|
||||
CONF_SELECT = "select"
|
||||
CONF_SWITCH = "switch"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_ON_TEXT = "on_text"
|
||||
CONF_OFF_TEXT = "off_text"
|
||||
|
|
|
@ -10,6 +10,8 @@ from esphome.const import (
|
|||
|
||||
CODEOWNERS = ["@glmnet", "@zuidwijk"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
|
|
0
esphome/components/ee895/__init__.py
Normal file
0
esphome/components/ee895/__init__.py
Normal file
115
esphome/components/ee895/ee895.cpp
Normal file
115
esphome/components/ee895/ee895.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "ee895.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ee895 {
|
||||
|
||||
static const char *const TAG = "ee895";
|
||||
|
||||
static const uint16_t CRC16_ONEWIRE_START = 0xFFFF;
|
||||
static const uint8_t FUNCTION_CODE_READ = 0x03;
|
||||
static const uint16_t SERIAL_NUMBER = 0x0000;
|
||||
static const uint16_t TEMPERATURE_ADDRESS = 0x03EA;
|
||||
static const uint16_t CO2_ADDRESS = 0x0424;
|
||||
static const uint16_t PRESSURE_ADDRESS = 0x04B0;
|
||||
|
||||
void EE895Component::setup() {
|
||||
uint16_t crc16_check = 0;
|
||||
ESP_LOGCONFIG(TAG, "Setting up EE895...");
|
||||
write_command_(SERIAL_NUMBER, 8);
|
||||
uint8_t serial_number[20];
|
||||
this->read(serial_number, 20);
|
||||
|
||||
crc16_check = (serial_number[19] << 8) + serial_number[18];
|
||||
if (crc16_check != calc_crc16_(serial_number, 19)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str());
|
||||
}
|
||||
|
||||
void EE895Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EE895:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with EE895 failed!");
|
||||
break;
|
||||
case CRC_CHECK_FAILED:
|
||||
ESP_LOGE(TAG, "The crc check failed");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
}
|
||||
|
||||
float EE895Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void EE895Component::update() {
|
||||
write_command_(TEMPERATURE_ADDRESS, 2);
|
||||
this->set_timeout(50, [this]() {
|
||||
float temperature = read_float_();
|
||||
|
||||
write_command_(CO2_ADDRESS, 2);
|
||||
float co2 = read_float_();
|
||||
|
||||
write_command_(PRESSURE_ADDRESS, 2);
|
||||
float pressure = read_float_();
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C co2=%.0fppm pressure=%.1f%mbar", temperature, co2, pressure);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->co2_sensor_ != nullptr)
|
||||
this->co2_sensor_->publish_state(co2);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
|
||||
uint8_t address[7];
|
||||
uint16_t crc16 = 0;
|
||||
address[0] = FUNCTION_CODE_READ;
|
||||
address[1] = (addr >> 8) & 0xFF;
|
||||
address[2] = addr & 0xFF;
|
||||
address[3] = (reg_cnt >> 8) & 0xFF;
|
||||
address[4] = reg_cnt & 0xFF;
|
||||
crc16 = calc_crc16_(address, 6);
|
||||
address[5] = crc16 & 0xFF;
|
||||
address[6] = (crc16 >> 8) & 0xFF;
|
||||
this->write(address, 7, true);
|
||||
}
|
||||
|
||||
float EE895Component::read_float_() {
|
||||
uint16_t crc16_check = 0;
|
||||
uint8_t i2c_response[8];
|
||||
this->read(i2c_response, 8);
|
||||
crc16_check = (i2c_response[7] << 8) + i2c_response[6];
|
||||
if (crc16_check != calc_crc16_(i2c_response, 7)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->status_set_warning();
|
||||
return 0;
|
||||
}
|
||||
uint32_t x = encode_uint32(i2c_response[4], i2c_response[5], i2c_response[2], i2c_response[3]);
|
||||
float value;
|
||||
memcpy(&value, &x, sizeof(value)); // convert uin32_t IEEE-754 format to float
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t EE895Component::calc_crc16_(const uint8_t buf[], uint8_t len) {
|
||||
uint8_t crc_check_buf[22];
|
||||
for (int i = 0; i < len; i++) {
|
||||
crc_check_buf[i + 1] = buf[i];
|
||||
}
|
||||
crc_check_buf[0] = this->address_;
|
||||
return crc16(crc_check_buf, len);
|
||||
}
|
||||
} // namespace ee895
|
||||
} // namespace esphome
|
34
esphome/components/ee895/ee895.h
Normal file
34
esphome/components/ee895/ee895.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ee895 {
|
||||
|
||||
/// This class implements support for the ee895 of temperature i2c sensors.
|
||||
class EE895Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
float get_setup_priority() const override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
void write_command_(uint16_t addr, uint16_t reg_cnt);
|
||||
float read_float_();
|
||||
uint16_t calc_crc16_(const uint8_t buf[], uint8_t len);
|
||||
sensor::Sensor *co2_sensor_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
|
||||
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace ee895
|
||||
} // namespace esphome
|
69
esphome/components/ee895/sensor.py
Normal file
69
esphome/components/ee895/sensor.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_CO2,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_CELSIUS,
|
||||
ICON_MOLECULE_CO2,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Stock-M"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ee895_ns = cg.esphome_ns.namespace("ee895")
|
||||
EE895Component = ee895_ns.class_("EE895Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EE895Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5F))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_CO2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure_sensor(sens))
|
|
@ -356,9 +356,14 @@ async def to_code(config):
|
|||
|
||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
else:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
|
||||
cg.add_define(
|
||||
"USE_ESP_IDF_VERSION_CODE",
|
||||
|
|
|
@ -123,11 +123,8 @@ def validate_gpio_pin(value):
|
|||
|
||||
def validate_supports(value):
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
@ -138,26 +135,6 @@ def validate_supports(value):
|
|||
)
|
||||
|
||||
value = _esp32_validations[variant].usage_validation(value)
|
||||
if CORE.using_arduino:
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid(
|
||||
"This pin mode is not supported on ESP32 for arduino frameworks",
|
||||
[CONF_MODE],
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -6,13 +6,21 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
|||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
|
||||
CONF_BLE_ID = "ble_id"
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
|
||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
|
||||
|
||||
GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
|
||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
|
||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <freertos/task.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
|
@ -31,24 +31,17 @@ void ESP32BLE::setup() {
|
|||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->start();
|
||||
#endif // USE_ESP32_BLE_SERVER
|
||||
|
||||
ESP_LOGD(TAG, "BLE setup complete");
|
||||
}
|
||||
|
||||
void ESP32BLE::mark_failed() {
|
||||
Component::mark_failed();
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
if (this->server_ != nullptr) {
|
||||
this->server_->mark_failed();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
|
@ -100,13 +93,16 @@ bool ESP32BLE::ble_setup_() {
|
|||
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
|
||||
if (!this->gap_event_handlers_.empty()) {
|
||||
err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->has_server()) {
|
||||
if (!this->gatts_event_handlers_.empty()) {
|
||||
err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
|
||||
|
@ -114,7 +110,7 @@ bool ESP32BLE::ble_setup_() {
|
|||
}
|
||||
}
|
||||
|
||||
if (this->has_client()) {
|
||||
if (!this->gattc_event_handlers_.empty()) {
|
||||
err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
|
||||
|
@ -158,6 +154,10 @@ void ESP32BLE::loop() {
|
|||
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
&ble_event->event_.gatts.gatts_param);
|
||||
break;
|
||||
case BLEEvent::GATTC:
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
break;
|
||||
case BLEEvent::GAP:
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
break;
|
||||
|
@ -176,9 +176,8 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa
|
|||
|
||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
||||
switch (event) {
|
||||
default:
|
||||
break;
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(event, param);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,14 +190,23 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat
|
|||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
this->server_->gatts_event_handler(event, gatts_if, param);
|
||||
#endif
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
// this->client_->gattc_event_handler(event, gattc_if, param);
|
||||
ESP_LOGV(TAG, "(BLE) gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
|
|
@ -5,17 +5,16 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "queue.h"
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||
#endif
|
||||
#include "queue.h"
|
||||
#include "ble_event.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
|
@ -26,28 +25,36 @@ typedef struct {
|
|||
uint16_t mtu;
|
||||
} conn_status_t;
|
||||
|
||||
class GAPEventHandler {
|
||||
public:
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GATTcEventHandler {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GATTsEventHandler {
|
||||
public:
|
||||
virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void mark_failed() override;
|
||||
|
||||
bool has_server() {
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
return this->server_ != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool has_client() { return false; }
|
||||
|
||||
BLEAdvertising *get_advertising() { return this->advertising_; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; }
|
||||
#endif
|
||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
|
||||
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
|
||||
|
||||
protected:
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
@ -59,9 +66,10 @@ class ESP32BLE : public Component {
|
|||
|
||||
bool ble_setup_();
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
esp32_ble_server::BLEServer *server_{nullptr};
|
||||
#endif
|
||||
std::vector<GAPEventHandler *> gap_event_handlers_;
|
||||
std::vector<GATTcEventHandler *> gattc_event_handlers_;
|
||||
std::vector<GATTsEventHandler *> gatts_event_handlers_;
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_;
|
||||
};
|
||||
|
|
|
@ -1,69 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
/*
|
||||
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
|
||||
* than trying to deal with various locking strategies, all incoming GAP and GATT
|
||||
* events will simply be placed on a semaphore guarded queue. The next time the
|
||||
* component runs loop(), these events are popped off the queue and handed at
|
||||
* this safer time.
|
||||
*/
|
||||
#include <esp_gatts_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
T *element = nullptr;
|
||||
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP and GATTC events are only queued, and get processed in the main loop().
|
||||
namespace esp32_ble {
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
class BLEEvent {
|
||||
public:
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
||||
this->type_ = 0;
|
||||
this->type_ = GAP;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
|
@ -84,26 +38,57 @@ class BLEEvent {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = 1;
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->data.assign(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
union {
|
||||
struct gap_event { // NOLINT(readability-identifier-naming)
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
struct gattc_event { // NOLINT(readability-identifier-naming)
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
} gatts;
|
||||
} event_;
|
||||
|
||||
std::vector<uint8_t> data{};
|
||||
uint8_t type_; // 0=gap 1=gattc
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
GATTS,
|
||||
} type_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
|
@ -27,8 +27,7 @@ ESPBTUUID ESPBTUUID::from_uint32(uint32_t uuid) {
|
|||
ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = data[i];
|
||||
memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128);
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
|
@ -91,10 +90,13 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
|||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = uuid.len;
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = uuid.uuid.uuid128[i];
|
||||
if (uuid.len == ESP_UUID_LEN_16) {
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
} else if (uuid.len == ESP_UUID_LEN_32) {
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
} else if (uuid.len == ESP_UUID_LEN_128) {
|
||||
memcpy(ret.uuid_.uuid.uuid128, uuid.uuid.uuid128, ESP_UUID_LEN_128);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::as_128bit() const {
|
||||
|
@ -158,30 +160,26 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() {
|
||||
char sbuf[64];
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
case ESP_UUID_LEN_32:
|
||||
sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
|
||||
(this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
|
||||
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
|
||||
this->uuid_.uuid.uuid32 & 0xff);
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
char *bpos = sbuf;
|
||||
std::string buf;
|
||||
for (int8_t i = 15; i >= 0; i--) {
|
||||
sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]);
|
||||
bpos += 2;
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 6 || i == 8 || i == 10 || i == 12)
|
||||
sprintf(bpos++, "-");
|
||||
buf += "-";
|
||||
}
|
||||
sbuf[47] = '\0';
|
||||
break;
|
||||
return buf;
|
||||
}
|
||||
return sbuf;
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
|
|
@ -32,9 +32,9 @@ class ESPBTUUID {
|
|||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid();
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string();
|
||||
std::string to_string() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
|
|
|
@ -2,16 +2,9 @@
|
|||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <queue>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
|
@ -57,84 +50,6 @@ template<class T> class Queue {
|
|||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
class BLEEvent {
|
||||
public:
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
||||
this->type_ = GAP;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
|
||||
// Need to also make a copy of notify event data.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of write data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->event_.gatts.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
uint8_t data[64];
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
uint8_t data[64];
|
||||
} gatts;
|
||||
} event_;
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
GATTS,
|
||||
} type_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ void BLEClientBase::loop() {
|
|||
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_)
|
||||
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
||||
return false;
|
||||
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||
return false;
|
||||
|
@ -138,6 +138,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||
this->address_str_.c_str(), ret);
|
||||
}
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
|
@ -189,6 +190,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
|
||||
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
|
||||
}
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
|
|
|
@ -7,21 +7,25 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
|||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_BLE_ID = "ble_id"
|
||||
|
||||
esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server")
|
||||
BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component)
|
||||
BLEServer = esp32_ble_server_ns.class_(
|
||||
"BLEServer",
|
||||
cg.Component,
|
||||
esp32_ble.GATTsEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent")
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEServer),
|
||||
cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string,
|
||||
cv.Optional(CONF_MODEL): cv.string,
|
||||
}
|
||||
|
@ -29,16 +33,18 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_BLE_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
||||
cg.add(parent.register_gatts_event_handler(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
cg.add(var.set_manufacturer(config[CONF_MANUFACTURER]))
|
||||
if CONF_MODEL in config:
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||
|
||||
cg.add(parent.set_server(var))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
|
|
@ -25,7 +25,8 @@ static const uint16_t VERSION_UUID = 0x2A26;
|
|||
static const uint16_t MANUFACTURER_UUID = 0x2A29;
|
||||
|
||||
void BLEServer::setup() {
|
||||
if (this->is_failed()) {
|
||||
if (this->parent_->is_failed()) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "BLE Server was marked failed by ESP32BLE");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "ble_service.h"
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
#include "esphome/components/esp32_ble/ble.h"
|
||||
#include "esphome/components/esp32_ble/ble_advertising.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
#include "esphome/components/esp32_ble/queue.h"
|
||||
|
@ -32,7 +33,7 @@ class BLEServiceComponent {
|
|||
virtual void stop();
|
||||
};
|
||||
|
||||
class BLEServer : public Component {
|
||||
class BLEServer : public Component, public GATTsEventHandler, public Parented<ESP32BLE> {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
@ -55,7 +56,8 @@ class BLEServer : public Component {
|
|||
uint32_t get_connected_client_count() { return this->connected_clients_; }
|
||||
const std::map<uint16_t, void *> &get_clients() { return this->clients_; }
|
||||
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) override;
|
||||
|
||||
void register_service_component(BLEServiceComponent *component) { this->service_components_.push_back(component); }
|
||||
|
||||
|
|
|
@ -15,10 +15,12 @@ from esphome.const import (
|
|||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
|
||||
)
|
||||
from esphome.components import esp32_ble
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components import esp32_ble
|
||||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
|
@ -27,7 +29,13 @@ CONF_WINDOW = "window"
|
|||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component)
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLETracker",
|
||||
cg.Component,
|
||||
esp32_ble.GAPEventHandler,
|
||||
esp32_ble.GATTcEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
|
||||
ESPBTDeviceListener = esp32_ble_tracker_ns.class_("ESPBTDeviceListener")
|
||||
ESPBTDevice = esp32_ble_tracker_ns.class_("ESPBTDevice")
|
||||
|
@ -138,6 +146,7 @@ def as_reversed_hex_array(value):
|
|||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLETracker),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -200,6 +209,12 @@ ESP_BLE_DEVICE_SCHEMA = cv.Schema(
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
||||
cg.add(parent.register_gap_event_handler(var))
|
||||
cg.add(parent.register_gattc_event_handler(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
params = config[CONF_SCAN_PARAMETERS]
|
||||
cg.add(var.set_scan_duration(params[CONF_DURATION]))
|
||||
cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625)))
|
||||
|
@ -248,6 +263,7 @@ async def to_code(config):
|
|||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
||||
|
||||
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
||||
cg.add_define("USE_ESP32_BLE_CLIENT")
|
||||
|
||||
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <freertos/task.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota/ota_component.h"
|
||||
|
@ -45,17 +45,19 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
|
|||
return u;
|
||||
}
|
||||
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
void ESP32BLETracker::setup() {
|
||||
if (this->parent_->is_failed()) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
||||
return;
|
||||
}
|
||||
|
||||
global_esp32_ble_tracker = this;
|
||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||
this->scan_end_lock_ = xSemaphoreCreateMutex();
|
||||
this->scanner_idle_ = true;
|
||||
if (!ESP32BLETracker::ble_setup()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota::global_ota_component->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t error) {
|
||||
|
@ -75,18 +77,6 @@ void ESP32BLETracker::setup() {
|
|||
}
|
||||
|
||||
void ESP32BLETracker::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
if (ble_event->type_) {
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
} else {
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
}
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
|
||||
int connecting = 0;
|
||||
int discovered = 0;
|
||||
int searching = 0;
|
||||
|
@ -238,85 +228,6 @@ void ESP32BLETracker::stop_scan() {
|
|||
this->cancel_timeout("scan");
|
||||
}
|
||||
|
||||
bool ESP32BLETracker::ble_setup() {
|
||||
// Initialize non-volatile storage for the bluetooth controller
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
err = esp_bt_controller_init(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
|
||||
;
|
||||
}
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
ESP_LOGE(TAG, "esp bt controller enable failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
err = esp_bluedroid_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_bluedroid_enable();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gap_register_callback(ESP32BLETracker::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gattc_register_callback(ESP32BLETracker::gattc_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty name
|
||||
esp_ble_gap_set_device_name("");
|
||||
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// BLE takes some time to be fully set up, 200ms should be more than enough
|
||||
delay(200); // NOLINT
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::start_scan_(bool first) {
|
||||
// The lock must be held when calling this function.
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
|
@ -369,11 +280,6 @@ void ESP32BLETracker::register_client(ESPBTClient *client) {
|
|||
}
|
||||
|
||||
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEEvent *gap_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_esp32_ble_tracker->ble_events_.push(gap_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
this->gap_scan_result_(param->scan_rst);
|
||||
|
@ -428,204 +334,11 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
|
|||
|
||||
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_esp32_ble_tracker->ble_events_.push(gattc_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
ESPBTUUID::ESPBTUUID() : uuid_() {}
|
||||
ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = uuid;
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_uint32(uint32_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = uuid;
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = data[i];
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
ESPBTUUID ret;
|
||||
if (data.length() == 4) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 8) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
||||
// investigated (lack of time)
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16);
|
||||
} else if (data.length() == 36) {
|
||||
// If the length of the string is 36 bytes then we will assume it is a long hex string in
|
||||
// UUID format.
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
int n = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
if (data.c_str()[i] == '-')
|
||||
i++;
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = uuid.len;
|
||||
if (uuid.len == ESP_UUID_LEN_16) {
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
} else if (uuid.len == ESP_UUID_LEN_32) {
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
} else if (uuid.len == ESP_UUID_LEN_128) {
|
||||
memcpy(ret.uuid_.uuid.uuid128, uuid.uuid.uuid128, ESP_UUID_LEN_128);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::as_128bit() const {
|
||||
if (this->uuid_.len == ESP_UUID_LEN_128) {
|
||||
return *this;
|
||||
}
|
||||
uint8_t data[] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint32_t uuid32;
|
||||
if (this->uuid_.len == ESP_UUID_LEN_32) {
|
||||
uuid32 = this->uuid_.uuid.uuid32;
|
||||
} else {
|
||||
uuid32 = this->uuid_.uuid.uuid16;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->uuid_.len; i++) {
|
||||
data[12 + i] = ((uuid32 >> i * 8) & 0xFF);
|
||||
}
|
||||
return ESPBTUUID::from_raw(data);
|
||||
}
|
||||
bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const {
|
||||
if (this->uuid_.len == ESP_UUID_LEN_16) {
|
||||
return (this->uuid_.uuid.uuid16 >> 8) == data2 && (this->uuid_.uuid.uuid16 & 0xFF) == data1;
|
||||
} else if (this->uuid_.len == ESP_UUID_LEN_32) {
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
bool a = ((this->uuid_.uuid.uuid32 >> i * 8) & 0xFF) == data1;
|
||||
bool b = ((this->uuid_.uuid.uuid32 >> (i + 1) * 8) & 0xFF) == data2;
|
||||
if (a && b)
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < 15; i++) {
|
||||
if (this->uuid_.uuid.uuid128[i] == data1 && this->uuid_.uuid.uuid128[i + 1] == data2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
if (this->uuid_.len == uuid.uuid_.len) {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
if (uuid.uuid_.uuid.uuid16 == this->uuid_.uuid.uuid16) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case ESP_UUID_LEN_32:
|
||||
if (uuid.uuid_.uuid.uuid32 == this->uuid_.uuid.uuid32) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case ESP_UUID_LEN_128:
|
||||
for (int i = 0; i < ESP_UUID_LEN_128; i++) {
|
||||
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return this->as_128bit() == uuid.as_128bit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
case ESP_UUID_LEN_32:
|
||||
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
|
||||
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
|
||||
this->uuid_.uuid.uuid32 & 0xff);
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
std::string buf;
|
||||
for (int8_t i = 15; i >= 0; i--) {
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 6 || i == 8 || i == 10 || i == 12)
|
||||
buf += "-";
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
uint64_t ESPBTUUID::get_128bit_high() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
||||
}
|
||||
uint64_t ESPBTUUID::get_128bit_low() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
||||
}
|
||||
|
||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
||||
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
|
||||
if (!data.uuid.contains(0x4C, 0x00))
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
@ -15,40 +14,16 @@
|
|||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include "esphome/components/esp32_ble/ble.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
class ESPBTUUID {
|
||||
public:
|
||||
ESPBTUUID();
|
||||
|
||||
static ESPBTUUID from_uint16(uint16_t uuid);
|
||||
|
||||
static ESPBTUUID from_uint32(uint32_t uuid);
|
||||
|
||||
static ESPBTUUID from_raw(const uint8_t *data);
|
||||
|
||||
static ESPBTUUID from_raw(const std::string &data);
|
||||
|
||||
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
|
||||
|
||||
ESPBTUUID as_128bit() const;
|
||||
|
||||
bool contains(uint8_t data1, uint8_t data2) const;
|
||||
|
||||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
uint64_t get_128bit_high() const;
|
||||
uint64_t get_128bit_low() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
};
|
||||
using namespace esp32_ble;
|
||||
|
||||
using adv_data_t = std::vector<uint8_t>;
|
||||
|
||||
|
@ -191,7 +166,7 @@ class ESPBTClient : public ESPBTDeviceListener {
|
|||
ClientState state_;
|
||||
};
|
||||
|
||||
class ESP32BLETracker : public Component {
|
||||
class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEventHandler, public Parented<ESP32BLE> {
|
||||
public:
|
||||
void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
|
||||
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
|
||||
|
@ -218,16 +193,15 @@ class ESP32BLETracker : public Component {
|
|||
void start_scan();
|
||||
void stop_scan();
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
/// The FreeRTOS task managing the bluetooth interface.
|
||||
static bool ble_setup();
|
||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||
void start_scan_(bool first);
|
||||
/// Called when a scan ends
|
||||
void end_of_scan_();
|
||||
/// Callback that will handle all GAP events and redistribute them to other callbacks.
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
|
||||
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
|
||||
|
@ -238,9 +212,6 @@ class ESP32BLETracker : public Component {
|
|||
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||
|
||||
int app_id_;
|
||||
/// Callback that will handle all GATTC events and redistribute them to other callbacks.
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
std::vector<uint64_t> already_discovered_;
|
||||
|
@ -263,8 +234,6 @@ class ESP32BLETracker : public Component {
|
|||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_buffer_[16];
|
||||
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
|
|
|
@ -156,7 +156,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
|||
cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum(
|
||||
FRAME_SIZES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=10, max=63),
|
||||
cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63),
|
||||
cv.Optional(CONF_CONTRAST, default=0): camera_range_param,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param,
|
||||
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
||||
|
|
|
@ -6,7 +6,7 @@ from esphome.const import CONF_ID
|
|||
|
||||
AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
DEPENDENCIES = ["wifi", "esp32"]
|
||||
|
||||
CONF_AUTHORIZED_DURATION = "authorized_duration"
|
||||
|
|
|
@ -195,7 +195,7 @@ void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
|
|||
}
|
||||
|
||||
void ESP32ImprovComponent::start() {
|
||||
if (this->state_ != improv::STATE_STOPPED)
|
||||
if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, "Setting Improv to start");
|
||||
|
|
|
@ -33,6 +33,7 @@ ETHERNET_TYPES = {
|
|||
"RTL8201": EthernetType.ETHERNET_TYPE_RTL8201,
|
||||
"DP83848": EthernetType.ETHERNET_TYPE_DP83848,
|
||||
"IP101": EthernetType.ETHERNET_TYPE_IP101,
|
||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||
}
|
||||
|
||||
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
||||
|
|
339
esphome/components/ethernet/esp_eth_phy_jl1101.c
Normal file
339
esphome/components/ethernet/esp_eth_phy_jl1101.c
Normal file
|
@ -0,0 +1,339 @@
|
|||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth.h"
|
||||
#include "eth_phy_regs_struct.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
static const char *TAG = "jl1101";
|
||||
#define PHY_CHECK(a, str, goto_tag, ...) \
|
||||
do { \
|
||||
if (!(a)) { \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/***************Vendor Specific Register***************/
|
||||
|
||||
/**
|
||||
* @brief PSR(Page Select Register)
|
||||
*
|
||||
*/
|
||||
typedef union {
|
||||
struct {
|
||||
uint16_t page_select : 8; /* Select register page, default is 0 */
|
||||
uint16_t reserved : 8; /* Reserved */
|
||||
};
|
||||
uint16_t val;
|
||||
} psr_reg_t;
|
||||
#define ETH_PHY_PSR_REG_ADDR (0x1F)
|
||||
|
||||
typedef struct {
|
||||
esp_eth_phy_t parent;
|
||||
esp_eth_mediator_t *eth;
|
||||
int addr;
|
||||
uint32_t reset_timeout_ms;
|
||||
uint32_t autonego_timeout_ms;
|
||||
eth_link_t link_status;
|
||||
int reset_gpio_num;
|
||||
} phy_jl1101_t;
|
||||
|
||||
static esp_err_t jl1101_page_select(phy_jl1101_t *jl1101, uint32_t page) {
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
psr_reg_t psr = {.page_select = page};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_PSR_REG_ADDR, psr.val) == ESP_OK, "write PSR failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_update_link_duplex_speed(phy_jl1101_t *jl1101) {
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
eth_speed_t speed = ETH_SPEED_10M;
|
||||
eth_duplex_t duplex = ETH_DUPLEX_HALF;
|
||||
bmcr_reg_t bmcr;
|
||||
bmsr_reg_t bmsr;
|
||||
uint32_t peer_pause_ability = false;
|
||||
anlpar_reg_t anlpar;
|
||||
PHY_CHECK(jl1101_page_select(jl1101, 0) == ESP_OK, "select page 0 failed", err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed",
|
||||
err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_ANLPAR_REG_ADDR, &(anlpar.val)) == ESP_OK,
|
||||
"read ANLPAR failed", err);
|
||||
eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
|
||||
/* check if link status changed */
|
||||
if (jl1101->link_status != link) {
|
||||
/* when link up, read negotiation result */
|
||||
if (link == ETH_LINK_UP) {
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (bmcr.speed_select) {
|
||||
speed = ETH_SPEED_100M;
|
||||
} else {
|
||||
speed = ETH_SPEED_10M;
|
||||
}
|
||||
if (bmcr.duplex_mode) {
|
||||
duplex = ETH_DUPLEX_FULL;
|
||||
} else {
|
||||
duplex = ETH_DUPLEX_HALF;
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *) speed) == ESP_OK, "change speed failed", err);
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *) duplex) == ESP_OK, "change duplex failed", err);
|
||||
/* if we're in duplex mode, and peer has the flow control ability */
|
||||
if (duplex == ETH_DUPLEX_FULL && anlpar.symmetric_pause) {
|
||||
peer_pause_ability = 1;
|
||||
} else {
|
||||
peer_pause_ability = 0;
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_PAUSE, (void *) peer_pause_ability) == ESP_OK,
|
||||
"change pause ability failed", err);
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *) link) == ESP_OK, "change link failed", err);
|
||||
jl1101->link_status = link;
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) {
|
||||
PHY_CHECK(eth, "can't set mediator to null", err);
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->eth = eth;
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_get_link(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
/* Updata information about link, speed, duplex */
|
||||
PHY_CHECK(jl1101_update_link_duplex_speed(jl1101) == ESP_OK, "update link duplex speed failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_reset(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
bmcr_reg_t bmcr = {.reset = 1};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
/* Wait for reset complete */
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->reset_timeout_ms / 50; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (!bmcr.reset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PHY_CHECK(to < jl1101->reset_timeout_ms / 50, "reset timeout", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
if (jl1101->reset_gpio_num >= 0) {
|
||||
esp_rom_gpio_pad_select_gpio(jl1101->reset_gpio_num);
|
||||
gpio_set_direction(jl1101->reset_gpio_num, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(jl1101->reset_gpio_num, 0);
|
||||
esp_rom_delay_us(100); // insert min input assert time
|
||||
gpio_set_level(jl1101->reset_gpio_num, 1);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
/* in case any link status has changed, let's assume we're in link down status */
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
/* Restart auto negotiation */
|
||||
bmcr_reg_t bmcr = {
|
||||
.speed_select = 1, /* 100Mbps */
|
||||
.duplex_mode = 1, /* Full Duplex */
|
||||
.en_auto_nego = 1, /* Auto Negotiation */
|
||||
.restart_auto_nego = 1 /* Restart Auto Negotiation */
|
||||
};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
/* Wait for auto negotiation complete */
|
||||
bmsr_reg_t bmsr;
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->autonego_timeout_ms / 100; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed",
|
||||
err);
|
||||
if (bmsr.auto_nego_complete) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Auto negotiation failed, maybe no network cable plugged in, so output a warning */
|
||||
if (to >= jl1101->autonego_timeout_ms / 100) {
|
||||
ESP_LOGW(TAG, "auto negotiation timeout");
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_pwrctl(esp_eth_phy_t *phy, bool enable) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
bmcr_reg_t bmcr;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (!enable) {
|
||||
/* Enable IEEE Power Down Mode */
|
||||
bmcr.power_down = 1;
|
||||
} else {
|
||||
/* Disable IEEE Power Down Mode */
|
||||
bmcr.power_down = 0;
|
||||
}
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
if (!enable) {
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
PHY_CHECK(bmcr.power_down == 1, "power down failed", err);
|
||||
} else {
|
||||
/* wait for power up complete */
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->reset_timeout_ms / 10; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (bmcr.power_down == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PHY_CHECK(to < jl1101->reset_timeout_ms / 10, "power up timeout", err);
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_set_addr(esp_eth_phy_t *phy, uint32_t addr) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->addr = addr;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_get_addr(esp_eth_phy_t *phy, uint32_t *addr) {
|
||||
PHY_CHECK(addr, "addr can't be null", err);
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
*addr = jl1101->addr;
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_del(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
free(jl1101);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_advertise_pause_ability(esp_eth_phy_t *phy, uint32_t ability) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
/* Set PAUSE function ability */
|
||||
anar_reg_t anar;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_ANAR_REG_ADDR, &(anar.val)) == ESP_OK, "read ANAR failed",
|
||||
err);
|
||||
if (ability) {
|
||||
anar.asymmetric_pause = 1;
|
||||
anar.symmetric_pause = 1;
|
||||
} else {
|
||||
anar.asymmetric_pause = 0;
|
||||
anar.symmetric_pause = 0;
|
||||
}
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_ANAR_REG_ADDR, anar.val) == ESP_OK, "write ANAR failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_init(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
// Detect PHY address
|
||||
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
|
||||
PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
|
||||
}
|
||||
/* Power on Ethernet PHY */
|
||||
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
|
||||
/* Reset Ethernet PHY */
|
||||
PHY_CHECK(jl1101_reset(phy) == ESP_OK, "reset failed", err);
|
||||
/* Check PHY ID */
|
||||
phyidr1_reg_t id1;
|
||||
phyidr2_reg_t id2;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_IDR1_REG_ADDR, &(id1.val)) == ESP_OK, "read ID1 failed", err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_IDR2_REG_ADDR, &(id2.val)) == ESP_OK, "read ID2 failed", err);
|
||||
PHY_CHECK(id1.oui_msb == 0x937C && id2.oui_lsb == 0x10 && id2.vendor_model == 0x2, "wrong chip ID", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_deinit(esp_eth_phy_t *phy) {
|
||||
/* Power off Ethernet PHY */
|
||||
PHY_CHECK(jl1101_pwrctl(phy, false) == ESP_OK, "power control failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) {
|
||||
PHY_CHECK(config, "can't set phy config to null", err);
|
||||
phy_jl1101_t *jl1101 = calloc(1, sizeof(phy_jl1101_t));
|
||||
PHY_CHECK(jl1101, "calloc jl1101 failed", err);
|
||||
jl1101->addr = config->phy_addr;
|
||||
jl1101->reset_gpio_num = config->reset_gpio_num;
|
||||
jl1101->reset_timeout_ms = config->reset_timeout_ms;
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
jl1101->autonego_timeout_ms = config->autonego_timeout_ms;
|
||||
jl1101->parent.reset = jl1101_reset;
|
||||
jl1101->parent.reset_hw = jl1101_reset_hw;
|
||||
jl1101->parent.init = jl1101_init;
|
||||
jl1101->parent.deinit = jl1101_deinit;
|
||||
jl1101->parent.set_mediator = jl1101_set_mediator;
|
||||
jl1101->parent.negotiate = jl1101_negotiate;
|
||||
jl1101->parent.get_link = jl1101_get_link;
|
||||
jl1101->parent.pwrctl = jl1101_pwrctl;
|
||||
jl1101->parent.get_addr = jl1101_get_addr;
|
||||
jl1101->parent.set_addr = jl1101_set_addr;
|
||||
jl1101->parent.advertise_pause_ability = jl1101_advertise_pause_ability;
|
||||
jl1101->parent.del = jl1101_del;
|
||||
|
||||
return &(jl1101->parent);
|
||||
err:
|
||||
return NULL;
|
||||
}
|
||||
#endif /* USE_ESP32 */
|
|
@ -71,6 +71,10 @@ void EthernetComponent::setup() {
|
|||
phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
break;
|
||||
}
|
||||
case ETHERNET_TYPE_JL1101: {
|
||||
phy = esp_eth_phy_new_jl1101(&phy_config);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
|
|
@ -18,6 +18,7 @@ enum EthernetType {
|
|||
ETHERNET_TYPE_RTL8201,
|
||||
ETHERNET_TYPE_DP83848,
|
||||
ETHERNET_TYPE_IP101,
|
||||
ETHERNET_TYPE_JL1101,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
|
@ -82,6 +83,7 @@ class EthernetComponent : public Component {
|
|||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern EthernetComponent *global_eth_component;
|
||||
extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config);
|
||||
|
||||
} // namespace ethernet
|
||||
} // namespace esphome
|
||||
|
|
|
@ -52,7 +52,7 @@ GrowattSolar = growatt_solar_ns.class_(
|
|||
PHASE_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
|
@ -71,7 +71,7 @@ PHASE_SENSORS = {
|
|||
PV_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
|
@ -135,13 +135,13 @@ CONFIG_SCHEMA = (
|
|||
),
|
||||
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
|
|
0
esphome/components/hte501/__init__.py
Normal file
0
esphome/components/hte501/__init__.py
Normal file
90
esphome/components/hte501/hte501.cpp
Normal file
90
esphome/components/hte501/hte501.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include "hte501.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hte501 {
|
||||
|
||||
static const char *const TAG = "hte501";
|
||||
|
||||
void HTE501Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up HTE501...");
|
||||
uint8_t address[] = {0x70, 0x29};
|
||||
this->write(address, 2, false);
|
||||
uint8_t identification[9];
|
||||
this->read(identification, 9);
|
||||
if (identification[8] != calc_crc8_(identification, 0, 7)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str());
|
||||
}
|
||||
|
||||
void HTE501Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HTE501:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with HTE501 failed!");
|
||||
break;
|
||||
case CRC_CHECK_FAILED:
|
||||
ESP_LOGE(TAG, "The crc check failed");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
float HTE501Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void HTE501Component::update() {
|
||||
uint8_t address_1[] = {0x2C, 0x1B};
|
||||
this->write(address_1, 2, true);
|
||||
this->set_timeout(50, [this]() {
|
||||
uint8_t i2c_response[6];
|
||||
this->read(i2c_response, 6);
|
||||
if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1) && i2c_response[5] != calc_crc8_(i2c_response, 3, 4)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float temperature = (float) encode_uint16(i2c_response[0], i2c_response[1]);
|
||||
if (temperature > 55536) {
|
||||
temperature = (temperature - 65536) / 100;
|
||||
} else {
|
||||
temperature = temperature / 100;
|
||||
}
|
||||
float humidity = ((float) encode_uint16(i2c_response[3], i2c_response[4])) / 100.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
unsigned char HTE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) {
|
||||
unsigned char crc_val = 0xFF;
|
||||
unsigned char i = 0;
|
||||
unsigned char j = 0;
|
||||
for (i = from; i <= to; i++) {
|
||||
int cur_val = buf[i];
|
||||
for (j = 0; j < 8; j++) {
|
||||
if (((crc_val ^ cur_val) & 0x80) != 0) // If MSBs are not equal
|
||||
{
|
||||
crc_val = ((crc_val << 1) ^ 0x31);
|
||||
} else {
|
||||
crc_val = (crc_val << 1);
|
||||
}
|
||||
cur_val = cur_val << 1;
|
||||
}
|
||||
}
|
||||
return crc_val;
|
||||
}
|
||||
} // namespace hte501
|
||||
} // namespace esphome
|
30
esphome/components/hte501/hte501.h
Normal file
30
esphome/components/hte501/hte501.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hte501 {
|
||||
|
||||
/// This class implements support for the hte501 of temperature i2c sensors.
|
||||
class HTE501Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
float get_setup_priority() const override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to);
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
|
||||
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace hte501
|
||||
} // namespace esphome
|
58
esphome/components/hte501/sensor.py
Normal file
58
esphome/components/hte501/sensor.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Stock-M"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
hte501_ns = cg.esphome_ns.namespace("hte501")
|
||||
HTE501Component = hte501_ns.class_(
|
||||
"HTE501Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HTE501Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
|
@ -195,6 +195,8 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|||
for conf in config.get(CONF_ON_RESPONSE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_response_trigger(trigger))
|
||||
await automation.build_automation(trigger, [(int, "status_code")], conf)
|
||||
await automation.build_automation(
|
||||
trigger, [(int, "status_code"), (cg.uint32, "duration_ms")], conf
|
||||
)
|
||||
|
||||
return var
|
||||
|
|
|
@ -66,6 +66,9 @@ void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *>
|
|||
}
|
||||
|
||||
this->client_.setTimeout(this->timeout_);
|
||||
#if defined(USE_ESP32)
|
||||
this->client_.setConnectTimeout(this->timeout_);
|
||||
#endif
|
||||
if (this->useragent_ != nullptr) {
|
||||
this->client_.setUserAgent(this->useragent_);
|
||||
}
|
||||
|
@ -73,25 +76,27 @@ void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *>
|
|||
this->client_.addHeader(header.name, header.value, false, true);
|
||||
}
|
||||
|
||||
uint32_t start_time = millis();
|
||||
int http_code = this->client_.sendRequest(this->method_, this->body_.c_str());
|
||||
uint32_t duration = millis() - start_time;
|
||||
for (auto *trigger : response_triggers)
|
||||
trigger->process(http_code);
|
||||
trigger->process(http_code, duration);
|
||||
|
||||
if (http_code < 0) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", this->url_.c_str(),
|
||||
HTTPClient::errorToString(http_code).c_str());
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(),
|
||||
HTTPClient::errorToString(http_code).c_str(), duration);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (http_code < 200 || http_code >= 300) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d", this->url_.c_str(), http_code);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d", this->url_.c_str(), http_code);
|
||||
ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
|
|
|
@ -31,7 +31,10 @@ struct Header {
|
|||
const char *value;
|
||||
};
|
||||
|
||||
class HttpRequestResponseTrigger;
|
||||
class HttpRequestResponseTrigger : public Trigger<int32_t, uint32_t> {
|
||||
public:
|
||||
void process(int32_t status_code, uint32_t duration_ms) { this->trigger(status_code, duration_ms); }
|
||||
};
|
||||
|
||||
class HttpRequestComponent : public Component {
|
||||
public:
|
||||
|
@ -138,11 +141,6 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
||||
};
|
||||
|
||||
class HttpRequestResponseTrigger : public Trigger<int> {
|
||||
public:
|
||||
void process(int status_code) { this->trigger(status_code); }
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@ from esphome.const import (
|
|||
CONF_MODEL,
|
||||
CONF_MOISTURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||
DEVICE_CLASS_PRECIPITATION,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
)
|
||||
|
@ -70,31 +72,31 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_R_INT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_INTENSITY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome import core, pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import (
|
||||
CONF_COLOR_PALETTE,
|
||||
|
@ -12,10 +12,11 @@ from esphome.const import (
|
|||
CONF_RAW_DATA_ID,
|
||||
CONF_RESET_PIN,
|
||||
)
|
||||
from esphome.core import HexInt
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
|
||||
CONF_LED_PIN = "led_pin"
|
||||
|
||||
ili9341_ns = cg.esphome_ns.namespace("ili9341")
|
||||
|
@ -37,7 +38,25 @@ MODELS = {
|
|||
|
||||
ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_")
|
||||
|
||||
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE")
|
||||
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get(
|
||||
CONF_COLOR_PALETTE_IMAGES
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette"
|
||||
)
|
||||
if (
|
||||
config.get(CONF_COLOR_PALETTE_IMAGES)
|
||||
and config.get(CONF_COLOR_PALETTE) != "IMAGE_ADAPTIVE"
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
|
@ -48,12 +67,16 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE,
|
||||
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
|
||||
cv.file_
|
||||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
.extend(spi.spi_device_schema(False)),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
@ -86,12 +109,45 @@ async def to_code(config):
|
|||
led_pin = await cg.gpio_pin_expression(config[CONF_LED_PIN])
|
||||
cg.add(var.set_led_pin(led_pin))
|
||||
|
||||
rhs = None
|
||||
if config[CONF_COLOR_PALETTE] == "GRAYSCALE":
|
||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED))
|
||||
rhs = []
|
||||
for x in range(256):
|
||||
rhs.extend([HexInt(x), HexInt(x), HexInt(x)])
|
||||
elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE":
|
||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED))
|
||||
from PIL import Image
|
||||
|
||||
def load_image(filename):
|
||||
path = CORE.relative_config_path(filename)
|
||||
try:
|
||||
return Image.open(path)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||
|
||||
# make a wide horizontal combined image.
|
||||
images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]]
|
||||
total_width = sum(i.width for i in images)
|
||||
max_height = max(i.height for i in images)
|
||||
|
||||
ref_image = Image.new("RGB", (total_width, max_height))
|
||||
x = 0
|
||||
for i in images:
|
||||
ref_image.paste(i, (x, 0))
|
||||
x = x + i.width
|
||||
|
||||
# reduce the colors on combined image to 256.
|
||||
converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256)
|
||||
# if you want to verify how the images look use
|
||||
# ref_image.save("ref_in.png")
|
||||
# converted.save("ref_out.png")
|
||||
palette = converted.getpalette()
|
||||
assert len(palette) == 256 * 3
|
||||
rhs = palette
|
||||
else:
|
||||
cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8))
|
||||
|
||||
if rhs is not None:
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.add(var.set_palette(prog_arr))
|
||||
else:
|
||||
pass
|
||||
|
|
|
@ -122,7 +122,12 @@ void ILI9341Display::display_() {
|
|||
}
|
||||
|
||||
void ILI9341Display::fill(Color color) {
|
||||
uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
|
||||
uint8_t color332 = 0;
|
||||
if (this->buffer_color_mode_ == BITS_8) {
|
||||
color332 = display::ColorUtil::color_to_332(color);
|
||||
} else { // if (this->buffer_color_mode_ == BITS_8_INDEXED)
|
||||
color332 = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
|
||||
}
|
||||
memset(this->buffer_, color332, this->get_buffer_length_());
|
||||
this->x_low_ = 0;
|
||||
this->y_low_ = 0;
|
||||
|
|
|
@ -26,6 +26,7 @@ IMAGE_TYPE = {
|
|||
"RGB24": ImageType.IMAGE_TYPE_RGB24,
|
||||
"TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
|
||||
"RGB565": ImageType.IMAGE_TYPE_RGB565,
|
||||
"TRANSPARENT_IMAGE": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
|
||||
}
|
||||
|
||||
Image_ = display.display_ns.class_("Image")
|
||||
|
@ -105,7 +106,7 @@ async def to_code(config):
|
|||
data[pos] = rgb & 255
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == "BINARY":
|
||||
elif (config[CONF_TYPE] == "BINARY") or (config[CONF_TYPE] == "TRANSPARENT_BINARY"):
|
||||
image = image.convert("1", dither=dither)
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
|
@ -116,7 +117,7 @@ async def to_code(config):
|
|||
pos = x + y * width8
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
elif config[CONF_TYPE] == "TRANSPARENT_BINARY":
|
||||
elif config[CONF_TYPE] == "TRANSPARENT_IMAGE":
|
||||
image = image.convert("RGBA")
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
|
|
42
esphome/components/improv_base/__init__.py
Normal file
42
esphome/components/improv_base/__init__.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import re
|
||||
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.const import __version__
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
CONF_NEXT_URL = "next_url"
|
||||
|
||||
VALID_SUBSTITUTIONS = ["esphome_version", "ip_address", "device_name"]
|
||||
|
||||
|
||||
def validate_next_url(value):
|
||||
value = cv.url(value)
|
||||
test = r"{{(?!" + r"\b|".join(VALID_SUBSTITUTIONS) + r"\b)(\w+)}}"
|
||||
result = re.search(test, value)
|
||||
if result:
|
||||
raise cv.Invalid(
|
||||
f"Invalid substitution(s) ({', '.join(result.groups())}) in next_url. Valid substitutions are: {', '.join(VALID_SUBSTITUTIONS)}"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
IMPROV_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_NEXT_URL): validate_next_url,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _process_next_url(url: str):
|
||||
if "{{esphome_version}}" in url:
|
||||
url = url.replace("{{esphome_version}}", __version__)
|
||||
return url
|
||||
|
||||
|
||||
async def setup_improv_core(var, config):
|
||||
if CONF_NEXT_URL in config:
|
||||
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
|
||||
cg.add_library("esphome/Improv", "1.2.3")
|
32
esphome/components/improv_base/improv_base.cpp
Normal file
32
esphome/components/improv_base/improv_base.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include "improv_base.h"
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_base {
|
||||
|
||||
std::string ImprovBase::get_formatted_next_url_() {
|
||||
if (this->next_url_.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::string copy = this->next_url_;
|
||||
// Device name
|
||||
std::size_t pos = this->next_url_.find("{{device_name}}");
|
||||
if (pos != std::string::npos) {
|
||||
const std::string &device_name = App.get_name();
|
||||
copy.replace(pos, 15, device_name);
|
||||
}
|
||||
|
||||
// Ip address
|
||||
pos = this->next_url_.find("{{ip_address}}");
|
||||
if (pos != std::string::npos) {
|
||||
std::string ip = network::IPAddress(network::get_ip_address()).str();
|
||||
copy.replace(pos, 14, ip);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
} // namespace improv_base
|
||||
} // namespace esphome
|
18
esphome/components/improv_base/improv_base.h
Normal file
18
esphome/components/improv_base/improv_base.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_base {
|
||||
|
||||
class ImprovBase {
|
||||
public:
|
||||
void set_next_url(const std::string &next_url) { this->next_url_ = next_url; }
|
||||
|
||||
protected:
|
||||
std::string get_formatted_next_url_();
|
||||
std::string next_url_;
|
||||
};
|
||||
|
||||
} // namespace improv_base
|
||||
} // namespace esphome
|
|
@ -4,7 +4,9 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components import improv_base
|
||||
|
||||
AUTO_LOAD = ["improv_base"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["logger", "wifi"]
|
||||
|
||||
|
@ -12,11 +14,15 @@ improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
|
|||
|
||||
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
|
||||
}
|
||||
)
|
||||
.extend(improv_base.IMPROV_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def validate_logger(config):
|
||||
|
@ -37,4 +43,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add_library("esphome/Improv", "1.2.3")
|
||||
await improv_base.setup_improv_core(var, config)
|
||||
|
|
|
@ -95,6 +95,9 @@ void ImprovSerialComponent::loop() {
|
|||
|
||||
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
|
||||
std::vector<std::string> urls;
|
||||
if (!this->next_url_.empty()) {
|
||||
urls.push_back(this->get_formatted_next_url_());
|
||||
}
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/improv_base/improv_base.h"
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
@ -27,7 +28,7 @@ enum ImprovSerialType : uint8_t {
|
|||
|
||||
static const uint8_t IMPROV_SERIAL_VERSION = 1;
|
||||
|
||||
class ImprovSerialComponent : public Component {
|
||||
class ImprovSerialComponent : public Component, public improv_base::ImprovBase {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#ifdef USE_ESP32
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace json {
|
||||
|
@ -24,6 +27,8 @@ std::string build_json(const json_build_t &f) {
|
|||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP32)
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
#elif defined(USE_RP2040)
|
||||
const size_t free_heap = rp2040.getFreeHeap();
|
||||
#endif
|
||||
|
||||
size_t request_size = std::min(free_heap, (size_t) 512);
|
||||
|
@ -64,6 +69,8 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
|||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP32)
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
#elif defined(USE_RP2040)
|
||||
const size_t free_heap = rp2040.getFreeHeap();
|
||||
#endif
|
||||
bool pass = false;
|
||||
size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5));
|
||||
|
|
95
esphome/components/key_collector/__init__.py
Normal file
95
esphome/components/key_collector/__init__.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import key_provider
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MAX_LENGTH,
|
||||
CONF_MIN_LENGTH,
|
||||
CONF_ON_TIMEOUT,
|
||||
CONF_SOURCE_ID,
|
||||
CONF_TIMEOUT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
AUTO_LOAD = ["key_provider"]
|
||||
|
||||
CONF_START_KEYS = "start_keys"
|
||||
CONF_END_KEYS = "end_keys"
|
||||
CONF_END_KEY_REQUIRED = "end_key_required"
|
||||
CONF_BACK_KEYS = "back_keys"
|
||||
CONF_CLEAR_KEYS = "clear_keys"
|
||||
CONF_ALLOWED_KEYS = "allowed_keys"
|
||||
CONF_ON_PROGRESS = "on_progress"
|
||||
CONF_ON_RESULT = "on_result"
|
||||
|
||||
key_collector_ns = cg.esphome_ns.namespace("key_collector")
|
||||
KeyCollector = key_collector_ns.class_("KeyCollector", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(KeyCollector),
|
||||
cv.GenerateID(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
|
||||
cv.Optional(CONF_MIN_LENGTH): cv.int_,
|
||||
cv.Optional(CONF_MAX_LENGTH): cv.int_,
|
||||
cv.Optional(CONF_START_KEYS): cv.string,
|
||||
cv.Optional(CONF_END_KEYS): cv.string,
|
||||
cv.Optional(CONF_END_KEY_REQUIRED): cv.boolean,
|
||||
cv.Optional(CONF_BACK_KEYS): cv.string,
|
||||
cv.Optional(CONF_CLEAR_KEYS): cv.string,
|
||||
cv.Optional(CONF_ALLOWED_KEYS): cv.string,
|
||||
cv.Optional(CONF_ON_PROGRESS): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_RESULT): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_TIMEOUT): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_END_KEYS, CONF_MAX_LENGTH),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
cg.add(var.set_provider(source))
|
||||
if CONF_MIN_LENGTH in config:
|
||||
cg.add(var.set_min_length(config[CONF_MIN_LENGTH]))
|
||||
if CONF_MAX_LENGTH in config:
|
||||
cg.add(var.set_max_length(config[CONF_MAX_LENGTH]))
|
||||
if CONF_START_KEYS in config:
|
||||
cg.add(var.set_start_keys(config[CONF_START_KEYS]))
|
||||
if CONF_END_KEYS in config:
|
||||
cg.add(var.set_end_keys(config[CONF_END_KEYS]))
|
||||
if CONF_END_KEY_REQUIRED in config:
|
||||
cg.add(var.set_end_key_required(config[CONF_END_KEY_REQUIRED]))
|
||||
if CONF_BACK_KEYS in config:
|
||||
cg.add(var.set_back_keys(config[CONF_BACK_KEYS]))
|
||||
if CONF_CLEAR_KEYS in config:
|
||||
cg.add(var.set_clear_keys(config[CONF_CLEAR_KEYS]))
|
||||
if CONF_ALLOWED_KEYS in config:
|
||||
cg.add(var.set_allowed_keys(config[CONF_ALLOWED_KEYS]))
|
||||
if CONF_ON_PROGRESS in config:
|
||||
await automation.build_automation(
|
||||
var.get_progress_trigger(),
|
||||
[(cg.std_string, "x"), (cg.uint8, "start")],
|
||||
config[CONF_ON_PROGRESS],
|
||||
)
|
||||
if CONF_ON_RESULT in config:
|
||||
await automation.build_automation(
|
||||
var.get_result_trigger(),
|
||||
[(cg.std_string, "x"), (cg.uint8, "start"), (cg.uint8, "end")],
|
||||
config[CONF_ON_RESULT],
|
||||
)
|
||||
if CONF_ON_TIMEOUT in config:
|
||||
await automation.build_automation(
|
||||
var.get_timeout_trigger(),
|
||||
[(cg.std_string, "x"), (cg.uint8, "start")],
|
||||
config[CONF_ON_TIMEOUT],
|
||||
)
|
||||
if CONF_TIMEOUT in config:
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
95
esphome/components/key_collector/key_collector.cpp
Normal file
95
esphome/components/key_collector/key_collector.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include "key_collector.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace key_collector {
|
||||
|
||||
static const char *const TAG = "key_collector";
|
||||
|
||||
KeyCollector::KeyCollector()
|
||||
: progress_trigger_(new Trigger<std::string, uint8_t>()),
|
||||
result_trigger_(new Trigger<std::string, uint8_t, uint8_t>()),
|
||||
timeout_trigger_(new Trigger<std::string, uint8_t>()) {}
|
||||
|
||||
void KeyCollector::loop() {
|
||||
if ((this->timeout_ == 0) || this->result_.empty() || (millis() - this->last_key_time_ < this->timeout_))
|
||||
return;
|
||||
this->timeout_trigger_->trigger(this->result_, this->start_key_);
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void KeyCollector::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Key Collector:");
|
||||
if (this->min_length_ > 0)
|
||||
ESP_LOGCONFIG(TAG, " min length: %d", this->min_length_);
|
||||
if (this->max_length_ > 0)
|
||||
ESP_LOGCONFIG(TAG, " max length: %d", this->max_length_);
|
||||
if (!this->back_keys_.empty())
|
||||
ESP_LOGCONFIG(TAG, " erase keys '%s'", this->back_keys_.c_str());
|
||||
if (!this->clear_keys_.empty())
|
||||
ESP_LOGCONFIG(TAG, " clear keys '%s'", this->clear_keys_.c_str());
|
||||
if (!this->start_keys_.empty())
|
||||
ESP_LOGCONFIG(TAG, " start keys '%s'", this->start_keys_.c_str());
|
||||
if (!this->end_keys_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " end keys '%s'", this->end_keys_.c_str());
|
||||
ESP_LOGCONFIG(TAG, " end key is required: %s", ONOFF(this->end_key_required_));
|
||||
}
|
||||
if (!this->allowed_keys_.empty())
|
||||
ESP_LOGCONFIG(TAG, " allowed keys '%s'", this->allowed_keys_.c_str());
|
||||
if (this->timeout_ > 0)
|
||||
ESP_LOGCONFIG(TAG, " entry timeout: %0.1f", this->timeout_ / 1000.0);
|
||||
}
|
||||
|
||||
void KeyCollector::set_provider(key_provider::KeyProvider *provider) {
|
||||
provider->add_on_key_callback([this](uint8_t key) { this->key_pressed_(key); });
|
||||
}
|
||||
|
||||
void KeyCollector::clear(bool progress_update) {
|
||||
this->result_.clear();
|
||||
this->start_key_ = 0;
|
||||
if (progress_update)
|
||||
this->progress_trigger_->trigger(this->result_, 0);
|
||||
}
|
||||
|
||||
void KeyCollector::key_pressed_(uint8_t key) {
|
||||
this->last_key_time_ = millis();
|
||||
if (!this->start_keys_.empty() && !this->start_key_) {
|
||||
if (this->start_keys_.find(key) != std::string::npos) {
|
||||
this->start_key_ = key;
|
||||
this->progress_trigger_->trigger(this->result_, this->start_key_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this->back_keys_.find(key) != std::string::npos) {
|
||||
if (!this->result_.empty()) {
|
||||
this->result_.pop_back();
|
||||
this->progress_trigger_->trigger(this->result_, this->start_key_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this->clear_keys_.find(key) != std::string::npos) {
|
||||
if (!this->result_.empty())
|
||||
this->clear();
|
||||
return;
|
||||
}
|
||||
if (this->end_keys_.find(key) != std::string::npos) {
|
||||
if ((this->min_length_ == 0) || (this->result_.size() >= this->min_length_)) {
|
||||
this->result_trigger_->trigger(this->result_, this->start_key_, key);
|
||||
this->clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this->allowed_keys_.empty() && (this->allowed_keys_.find(key) == std::string::npos))
|
||||
return;
|
||||
if ((this->max_length_ == 0) || (this->result_.size() < this->max_length_))
|
||||
this->result_.push_back(key);
|
||||
if ((this->max_length_ > 0) && (this->result_.size() == this->max_length_) && (!this->end_key_required_)) {
|
||||
this->result_trigger_->trigger(this->result_, this->start_key_, 0);
|
||||
this->clear(false);
|
||||
}
|
||||
this->progress_trigger_->trigger(this->result_, this->start_key_);
|
||||
}
|
||||
|
||||
} // namespace key_collector
|
||||
} // namespace esphome
|
52
esphome/components/key_collector/key_collector.h
Normal file
52
esphome/components/key_collector/key_collector.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include "esphome/components/key_provider/key_provider.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace key_collector {
|
||||
|
||||
class KeyCollector : public Component {
|
||||
public:
|
||||
KeyCollector();
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void set_provider(key_provider::KeyProvider *provider);
|
||||
void set_min_length(int min_length) { this->min_length_ = min_length; };
|
||||
void set_max_length(int max_length) { this->max_length_ = max_length; };
|
||||
void set_start_keys(std::string start_keys) { this->start_keys_ = std::move(start_keys); };
|
||||
void set_end_keys(std::string end_keys) { this->end_keys_ = std::move(end_keys); };
|
||||
void set_end_key_required(bool end_key_required) { this->end_key_required_ = end_key_required; };
|
||||
void set_back_keys(std::string back_keys) { this->back_keys_ = std::move(back_keys); };
|
||||
void set_clear_keys(std::string clear_keys) { this->clear_keys_ = std::move(clear_keys); };
|
||||
void set_allowed_keys(std::string allowed_keys) { this->allowed_keys_ = std::move(allowed_keys); };
|
||||
Trigger<std::string, uint8_t> *get_progress_trigger() const { return this->progress_trigger_; };
|
||||
Trigger<std::string, uint8_t, uint8_t> *get_result_trigger() const { return this->result_trigger_; };
|
||||
Trigger<std::string, uint8_t> *get_timeout_trigger() const { return this->timeout_trigger_; };
|
||||
void set_timeout(int timeout) { this->timeout_ = timeout; };
|
||||
|
||||
void clear(bool progress_update = true);
|
||||
|
||||
protected:
|
||||
void key_pressed_(uint8_t key);
|
||||
|
||||
int min_length_{0};
|
||||
int max_length_{0};
|
||||
std::string start_keys_;
|
||||
std::string end_keys_;
|
||||
bool end_key_required_{false};
|
||||
std::string back_keys_;
|
||||
std::string clear_keys_;
|
||||
std::string allowed_keys_;
|
||||
std::string result_;
|
||||
uint8_t start_key_{0};
|
||||
Trigger<std::string, uint8_t> *progress_trigger_;
|
||||
Trigger<std::string, uint8_t, uint8_t> *result_trigger_;
|
||||
Trigger<std::string, uint8_t> *timeout_trigger_;
|
||||
uint32_t last_key_time_;
|
||||
uint32_t timeout_{0};
|
||||
};
|
||||
|
||||
} // namespace key_collector
|
||||
} // namespace esphome
|
6
esphome/components/key_provider/__init__.py
Normal file
6
esphome/components/key_provider/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
key_provider_ns = cg.esphome_ns.namespace("key_provider")
|
||||
KeyProvider = key_provider_ns.class_("KeyProvider")
|
13
esphome/components/key_provider/key_provider.cpp
Normal file
13
esphome/components/key_provider/key_provider.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "key_provider.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace key_provider {
|
||||
|
||||
void KeyProvider::add_on_key_callback(std::function<void(uint8_t)> &&callback) {
|
||||
this->key_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void KeyProvider::send_key_(uint8_t key) { this->key_callback_.call(key); }
|
||||
|
||||
} // namespace key_provider
|
||||
} // namespace esphome
|
21
esphome/components/key_provider/key_provider.h
Normal file
21
esphome/components/key_provider/key_provider.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace key_provider {
|
||||
|
||||
/// interface for components that provide keypresses
|
||||
class KeyProvider {
|
||||
public:
|
||||
void add_on_key_callback(std::function<void(uint8_t)> &&callback);
|
||||
|
||||
protected:
|
||||
void send_key_(uint8_t key);
|
||||
|
||||
CallbackManager<void(uint8_t)> key_callback_{};
|
||||
};
|
||||
|
||||
} // namespace key_provider
|
||||
} // namespace esphome
|
158
esphome/components/ld2410/__init__.py
Normal file
158
esphome/components/ld2410/__init__.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID, CONF_TIMEOUT
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
CODEOWNERS = ["@sebcaps"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ld2410_ns = cg.esphome_ns.namespace("ld2410")
|
||||
LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice)
|
||||
LD2410Restart = ld2410_ns.class_("LD2410Restart", automation.Action)
|
||||
CONF_LD2410_ID = "ld2410_id"
|
||||
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
|
||||
CONF_MAX_STILL_DISTANCE = "max_still_distance"
|
||||
CONF_G0_MOVE_THRESHOLD = "g0_move_threshold"
|
||||
CONF_G0_STILL_THRESHOLD = "g0_still_threshold"
|
||||
CONF_G1_MOVE_THRESHOLD = "g1_move_threshold"
|
||||
CONF_G1_STILL_THRESHOLD = "g1_still_threshold"
|
||||
CONF_G2_MOVE_THRESHOLD = "g2_move_threshold"
|
||||
CONF_G2_STILL_THRESHOLD = "g2_still_threshold"
|
||||
CONF_G3_MOVE_THRESHOLD = "g3_move_threshold"
|
||||
CONF_G3_STILL_THRESHOLD = "g3_still_threshold"
|
||||
CONF_G4_MOVE_THRESHOLD = "g4_move_threshold"
|
||||
CONF_G4_STILL_THRESHOLD = "g4_still_threshold"
|
||||
CONF_G5_MOVE_THRESHOLD = "g5_move_threshold"
|
||||
CONF_G5_STILL_THRESHOLD = "g5_still_threshold"
|
||||
CONF_G6_MOVE_THRESHOLD = "g6_move_threshold"
|
||||
CONF_G6_STILL_THRESHOLD = "g6_still_threshold"
|
||||
CONF_G7_MOVE_THRESHOLD = "g7_move_threshold"
|
||||
CONF_G7_STILL_THRESHOLD = "g7_still_threshold"
|
||||
CONF_G8_MOVE_THRESHOLD = "g8_move_threshold"
|
||||
CONF_G8_STILL_THRESHOLD = "g8_still_threshold"
|
||||
|
||||
DISTANCES = [0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2410Component),
|
||||
cv.Optional(CONF_MAX_MOVE_DISTANCE, default="4.5m"): cv.All(
|
||||
cv.distance, cv.one_of(*DISTANCES, float=True)
|
||||
),
|
||||
cv.Optional(CONF_MAX_STILL_DISTANCE, default="4.5m"): cv.All(
|
||||
cv.distance, cv.one_of(*DISTANCES, float=True)
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT, default="5s"): cv.All(
|
||||
cv.positive_time_period_seconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=32767)),
|
||||
),
|
||||
cv.Optional(CONF_G0_MOVE_THRESHOLD, default=50): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G0_STILL_THRESHOLD, default=0): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G1_MOVE_THRESHOLD, default=50): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G1_STILL_THRESHOLD, default=0): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G2_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G2_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G3_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G3_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G4_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G4_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G5_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G5_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G6_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G6_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G7_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G7_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G8_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G8_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"ld2410",
|
||||
baud_rate=256000,
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_max_move_distance(int(config[CONF_MAX_MOVE_DISTANCE] / 0.75)))
|
||||
cg.add(var.set_max_still_distance(int(config[CONF_MAX_STILL_DISTANCE] / 0.75)))
|
||||
cg.add(
|
||||
var.set_range_config(
|
||||
config[CONF_G0_MOVE_THRESHOLD],
|
||||
config[CONF_G0_STILL_THRESHOLD],
|
||||
config[CONF_G1_MOVE_THRESHOLD],
|
||||
config[CONF_G1_STILL_THRESHOLD],
|
||||
config[CONF_G2_MOVE_THRESHOLD],
|
||||
config[CONF_G2_STILL_THRESHOLD],
|
||||
config[CONF_G3_MOVE_THRESHOLD],
|
||||
config[CONF_G3_STILL_THRESHOLD],
|
||||
config[CONF_G4_MOVE_THRESHOLD],
|
||||
config[CONF_G4_STILL_THRESHOLD],
|
||||
config[CONF_G5_MOVE_THRESHOLD],
|
||||
config[CONF_G5_STILL_THRESHOLD],
|
||||
config[CONF_G6_MOVE_THRESHOLD],
|
||||
config[CONF_G6_STILL_THRESHOLD],
|
||||
config[CONF_G7_MOVE_THRESHOLD],
|
||||
config[CONF_G7_STILL_THRESHOLD],
|
||||
config[CONF_G8_MOVE_THRESHOLD],
|
||||
config[CONF_G8_STILL_THRESHOLD],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LD2410Component),
|
||||
}
|
||||
)
|
36
esphome/components/ld2410/binary_sensor.py
Normal file
36
esphome/components/ld2410/binary_sensor.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY
|
||||
from . import CONF_LD2410_ID, LD2410Component
|
||||
|
||||
DEPENDENCIES = ["ld2410"]
|
||||
CONF_HAS_TARGET = "has_target"
|
||||
CONF_HAS_MOVING_TARGET = "has_moving_target"
|
||||
CONF_HAS_STILL_TARGET = "has_still_target"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY
|
||||
),
|
||||
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOTION
|
||||
),
|
||||
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if CONF_HAS_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
|
||||
cg.add(ld2410_component.set_target_sensor(sens))
|
||||
if CONF_HAS_MOVING_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_MOVING_TARGET])
|
||||
cg.add(ld2410_component.set_moving_target_sensor(sens))
|
||||
if CONF_HAS_STILL_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_STILL_TARGET])
|
||||
cg.add(ld2410_component.set_still_target_sensor(sens))
|
315
esphome/components/ld2410/ld2410.cpp
Normal file
315
esphome/components/ld2410/ld2410.cpp
Normal file
|
@ -0,0 +1,315 @@
|
|||
#include "ld2410.h"
|
||||
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
static const char *const TAG = "ld2410";
|
||||
|
||||
void LD2410Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2410:");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "HasTargetSensor", this->target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "MovingSensor", this->moving_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "StillSensor", this->still_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Moving Distance", this->moving_target_distance_sensor_);
|
||||
LOG_SENSOR(" ", "Still Distance", this->still_target_distance_sensor_);
|
||||
LOG_SENSOR(" ", "Moving Energy", this->moving_target_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Still Energy", this->still_target_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Detection Distance", this->detection_distance_sensor_);
|
||||
#endif
|
||||
this->set_config_mode_(true);
|
||||
this->get_version_();
|
||||
this->set_config_mode_(false);
|
||||
ESP_LOGCONFIG(TAG, " Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2],
|
||||
this->version_[3], this->version_[4], this->version_[5]);
|
||||
}
|
||||
|
||||
void LD2410Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up LD2410...");
|
||||
this->set_config_mode_(true);
|
||||
this->set_max_distances_timeout_(this->max_move_distance_, this->max_still_distance_, this->timeout_);
|
||||
// Configure Gates sensitivity
|
||||
this->set_gate_threshold_(0, this->rg0_move_threshold_, this->rg0_still_threshold_);
|
||||
this->set_gate_threshold_(1, this->rg1_move_threshold_, this->rg1_still_threshold_);
|
||||
this->set_gate_threshold_(2, this->rg2_move_threshold_, this->rg2_still_threshold_);
|
||||
this->set_gate_threshold_(3, this->rg3_move_threshold_, this->rg3_still_threshold_);
|
||||
this->set_gate_threshold_(4, this->rg4_move_threshold_, this->rg4_still_threshold_);
|
||||
this->set_gate_threshold_(5, this->rg5_move_threshold_, this->rg5_still_threshold_);
|
||||
this->set_gate_threshold_(6, this->rg6_move_threshold_, this->rg6_still_threshold_);
|
||||
this->set_gate_threshold_(7, this->rg7_move_threshold_, this->rg7_still_threshold_);
|
||||
this->set_gate_threshold_(8, this->rg8_move_threshold_, this->rg8_still_threshold_);
|
||||
this->get_version_();
|
||||
this->set_config_mode_(false);
|
||||
ESP_LOGCONFIG(TAG, "Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2],
|
||||
this->version_[3], this->version_[4], this->version_[5]);
|
||||
ESP_LOGCONFIG(TAG, "LD2410 setup complete.");
|
||||
}
|
||||
|
||||
void LD2410Component::loop() {
|
||||
const int max_line_length = 80;
|
||||
static uint8_t buffer[max_line_length];
|
||||
|
||||
while (available()) {
|
||||
this->readline_(read(), buffer, max_line_length);
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::send_command_(uint8_t command, uint8_t *command_value, int command_value_len) {
|
||||
// lastCommandSuccess->publish_state(false);
|
||||
|
||||
// frame start bytes
|
||||
this->write_array(CMD_FRAME_HEADER, 4);
|
||||
// length bytes
|
||||
int len = 2;
|
||||
if (command_value != nullptr)
|
||||
len += command_value_len;
|
||||
this->write_byte(lowbyte(len));
|
||||
this->write_byte(highbyte(len));
|
||||
|
||||
// command
|
||||
this->write_byte(lowbyte(command));
|
||||
this->write_byte(highbyte(command));
|
||||
|
||||
// command value bytes
|
||||
if (command_value != nullptr) {
|
||||
for (int i = 0; i < command_value_len; i++) {
|
||||
this->write_byte(command_value[i]);
|
||||
}
|
||||
}
|
||||
// frame end bytes
|
||||
this->write_array(CMD_FRAME_END, 4);
|
||||
// FIXME to remove
|
||||
delay(50); // NOLINT
|
||||
}
|
||||
|
||||
void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
if (len < 12)
|
||||
return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes
|
||||
if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) // check 4 frame start bytes
|
||||
return;
|
||||
if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values
|
||||
return; // data head=0xAA, data end=0x55, crc=0x00
|
||||
|
||||
/*
|
||||
Data Type: 6th
|
||||
0x01: Engineering mode
|
||||
0x02: Normal mode
|
||||
*/
|
||||
// char data_type = buffer[DATA_TYPES];
|
||||
/*
|
||||
Target states: 9th
|
||||
0x00 = No target
|
||||
0x01 = Moving targets
|
||||
0x02 = Still targets
|
||||
0x03 = Moving+Still targets
|
||||
*/
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
char target_state = buffer[TARGET_STATES];
|
||||
if (this->target_binary_sensor_ != nullptr) {
|
||||
this->target_binary_sensor_->publish_state(target_state != 0x00);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
Reduce data update rate to prevent home assistant database size grow fast
|
||||
*/
|
||||
int32_t current_millis = millis();
|
||||
if (current_millis - last_periodic_millis < 1000)
|
||||
return;
|
||||
last_periodic_millis = current_millis;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->moving_binary_sensor_ != nullptr) {
|
||||
this->moving_binary_sensor_->publish_state(CHECK_BIT(target_state, 0));
|
||||
}
|
||||
if (this->still_binary_sensor_ != nullptr) {
|
||||
this->still_binary_sensor_->publish_state(CHECK_BIT(target_state, 1));
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
#ifdef USE_SENSOR
|
||||
if (this->moving_target_distance_sensor_ != nullptr) {
|
||||
int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
|
||||
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
|
||||
}
|
||||
if (this->moving_target_energy_sensor_ != nullptr) {
|
||||
int new_moving_target_energy = buffer[MOVING_ENERGY];
|
||||
if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy)
|
||||
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
|
||||
}
|
||||
if (this->still_target_distance_sensor_ != nullptr) {
|
||||
int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
|
||||
this->still_target_distance_sensor_->publish_state(new_still_target_distance);
|
||||
}
|
||||
if (this->still_target_energy_sensor_ != nullptr) {
|
||||
int new_still_target_energy = buffer[STILL_ENERGY];
|
||||
if (this->still_target_energy_sensor_->get_state() != new_still_target_energy)
|
||||
this->still_target_energy_sensor_->publish_state(new_still_target_energy);
|
||||
}
|
||||
if (this->detection_distance_sensor_ != nullptr) {
|
||||
int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
if (this->detection_distance_sensor_->get_state() != new_detect_distance)
|
||||
this->detection_distance_sensor_->publish_state(new_detect_distance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND");
|
||||
if (len < 10) {
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect length");
|
||||
return;
|
||||
}
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect Header");
|
||||
return;
|
||||
}
|
||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGE(TAG, "Error with last command : status != 0x01");
|
||||
return;
|
||||
}
|
||||
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
||||
ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (buffer[COMMAND]) {
|
||||
case lowbyte(CMD_ENABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Enable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_DISABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Disabled conf command");
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
ESP_LOGV(TAG, "FW Version is: %u.%u.%u%u%u%u", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||
buffer[14]);
|
||||
this->version_[0] = buffer[13];
|
||||
this->version_[1] = buffer[12];
|
||||
this->version_[2] = buffer[17];
|
||||
this->version_[3] = buffer[16];
|
||||
this->version_[4] = buffer[15];
|
||||
this->version_[5] = buffer[14];
|
||||
|
||||
break;
|
||||
case lowbyte(CMD_GATE_SENS):
|
||||
ESP_LOGV(TAG, "Handled sensitivity command");
|
||||
break;
|
||||
case lowbyte(CMD_QUERY): // Query parameters response
|
||||
{
|
||||
if (buffer[10] != 0xAA)
|
||||
return; // value head=0xAA
|
||||
/*
|
||||
Moving distance range: 13th byte
|
||||
Still distance range: 14th byte
|
||||
*/
|
||||
// TODO
|
||||
// maxMovingDistanceRange->publish_state(buffer[12]);
|
||||
// maxStillDistanceRange->publish_state(buffer[13]);
|
||||
/*
|
||||
Moving Sensitivities: 15~23th bytes
|
||||
Still Sensitivities: 24~32th bytes
|
||||
*/
|
||||
for (int i = 0; i < 9; i++) {
|
||||
moving_sensitivities[i] = buffer[14 + i];
|
||||
}
|
||||
for (int i = 0; i < 9; i++) {
|
||||
still_sensitivities[i] = buffer[23 + i];
|
||||
}
|
||||
/*
|
||||
None Duration: 33~34th bytes
|
||||
*/
|
||||
// noneDuration->publish_state(this->two_byte_to_int_(buffer[32], buffer[33]));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::readline_(int readch, uint8_t *buffer, int len) {
|
||||
static int pos = 0;
|
||||
|
||||
if (readch >= 0) {
|
||||
if (pos < len - 1) {
|
||||
buffer[pos++] = readch;
|
||||
buffer[pos] = 0;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
if (pos >= 4) {
|
||||
if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) {
|
||||
ESP_LOGV(TAG, "Will handle Periodic Data");
|
||||
this->handle_periodic_data_(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
} else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 &&
|
||||
buffer[pos - 1] == 0x01) {
|
||||
ESP_LOGV(TAG, "Will handle ACK Data");
|
||||
this->handle_ack_data_(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::set_config_mode_(bool enable) {
|
||||
uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
|
||||
uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
|
||||
}
|
||||
|
||||
void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); }
|
||||
void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
|
||||
|
||||
void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range,
|
||||
uint16_t timeout) {
|
||||
uint8_t value[18] = {0x00,
|
||||
0x00,
|
||||
lowbyte(max_moving_distance_range),
|
||||
highbyte(max_moving_distance_range),
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
lowbyte(max_still_distance_range),
|
||||
highbyte(max_still_distance_range),
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
lowbyte(timeout),
|
||||
highbyte(timeout),
|
||||
0x00,
|
||||
0x00};
|
||||
this->send_command_(CMD_MAXDIST_DURATION, value, 18);
|
||||
this->query_parameters_();
|
||||
}
|
||||
void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens) {
|
||||
// reference
|
||||
// https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH
|
||||
// Send data: configure the motion sensitivity of distance gate 3 to 40, and the static sensitivity of 40
|
||||
// 00 00 (gate)
|
||||
// 03 00 00 00 (gate number)
|
||||
// 01 00 (motion sensitivity)
|
||||
// 28 00 00 00 (value)
|
||||
// 02 00 (still sensitivtiy)
|
||||
// 28 00 00 00 (value)
|
||||
uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00,
|
||||
0x01, 0x00, lowbyte(motionsens), highbyte(motionsens), 0x00, 0x00,
|
||||
0x02, 0x00, lowbyte(stillsens), highbyte(stillsens), 0x00, 0x00};
|
||||
this->send_command_(CMD_GATE_SENS, value, 18);
|
||||
}
|
||||
|
||||
} // namespace ld2410
|
||||
} // namespace esphome
|
146
esphome/components/ld2410/ld2410.h
Normal file
146
esphome/components/ld2410/ld2410.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
|
||||
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x0060;
|
||||
static const uint8_t CMD_QUERY = 0x0061;
|
||||
static const uint8_t CMD_GATE_SENS = 0x0064;
|
||||
static const uint8_t CMD_VERSION = 0x00A0;
|
||||
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x0001;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x0002;
|
||||
// Command Header & Footer
|
||||
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
Target states: 9th byte
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
enum PeriodicDataStructure : uint8_t {
|
||||
DATA_TYPES = 5,
|
||||
TARGET_STATES = 8,
|
||||
MOVING_TARGET_LOW = 9,
|
||||
MOVING_TARGET_HIGH = 10,
|
||||
MOVING_ENERGY = 11,
|
||||
STILL_TARGET_LOW = 12,
|
||||
STILL_TARGET_HIGH = 13,
|
||||
STILL_ENERGY = 14,
|
||||
DETECT_DISTANCE_LOW = 15,
|
||||
DETECT_DISTANCE_HIGH = 16,
|
||||
};
|
||||
enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 };
|
||||
|
||||
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
|
||||
|
||||
// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
|
||||
class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(moving_target_distance)
|
||||
SUB_SENSOR(still_target_distance)
|
||||
SUB_SENSOR(moving_target_energy)
|
||||
SUB_SENSOR(still_target_energy)
|
||||
SUB_SENSOR(detection_distance)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_target_sensor(binary_sensor::BinarySensor *sens) { this->target_binary_sensor_ = sens; };
|
||||
void set_moving_target_sensor(binary_sensor::BinarySensor *sens) { this->moving_binary_sensor_ = sens; };
|
||||
void set_still_target_sensor(binary_sensor::BinarySensor *sens) { this->still_binary_sensor_ = sens; };
|
||||
#endif
|
||||
|
||||
void set_timeout(uint16_t value) { this->timeout_ = value; };
|
||||
void set_max_move_distance(uint8_t value) { this->max_move_distance_ = value; };
|
||||
void set_max_still_distance(uint8_t value) { this->max_still_distance_ = value; };
|
||||
void set_range_config(int rg0_move, int rg0_still, int rg1_move, int rg1_still, int rg2_move, int rg2_still,
|
||||
int rg3_move, int rg3_still, int rg4_move, int rg4_still, int rg5_move, int rg5_still,
|
||||
int rg6_move, int rg6_still, int rg7_move, int rg7_still, int rg8_move, int rg8_still) {
|
||||
this->rg0_move_threshold_ = rg0_move;
|
||||
this->rg0_still_threshold_ = rg0_still;
|
||||
this->rg1_move_threshold_ = rg1_move;
|
||||
this->rg1_still_threshold_ = rg1_still;
|
||||
this->rg2_move_threshold_ = rg2_move;
|
||||
this->rg2_still_threshold_ = rg2_still;
|
||||
this->rg3_move_threshold_ = rg3_move;
|
||||
this->rg3_still_threshold_ = rg3_still;
|
||||
this->rg4_move_threshold_ = rg4_move;
|
||||
this->rg4_still_threshold_ = rg4_still;
|
||||
this->rg5_move_threshold_ = rg5_move;
|
||||
this->rg5_still_threshold_ = rg5_still;
|
||||
this->rg6_move_threshold_ = rg6_move;
|
||||
this->rg6_still_threshold_ = rg6_still;
|
||||
this->rg7_move_threshold_ = rg7_move;
|
||||
this->rg7_still_threshold_ = rg7_still;
|
||||
this->rg8_move_threshold_ = rg8_move;
|
||||
this->rg8_still_threshold_ = rg8_still;
|
||||
};
|
||||
int moving_sensitivities[9] = {0};
|
||||
int still_sensitivities[9] = {0};
|
||||
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *target_binary_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *moving_binary_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *still_binary_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t)(secondbyte << 8) + firstbyte; }
|
||||
void send_command_(uint8_t command_str, uint8_t *command_value, int command_value_len);
|
||||
|
||||
void set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range,
|
||||
uint16_t timeout);
|
||||
void set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_(uint8_t *buffer, int len);
|
||||
void handle_ack_data_(uint8_t *buffer, int len);
|
||||
void readline_(int readch, uint8_t *buffer, int len);
|
||||
void query_parameters_();
|
||||
void get_version_();
|
||||
|
||||
uint16_t timeout_;
|
||||
uint8_t max_move_distance_;
|
||||
uint8_t max_still_distance_;
|
||||
|
||||
uint8_t version_[6];
|
||||
uint8_t rg0_move_threshold_, rg0_still_threshold_, rg1_move_threshold_, rg1_still_threshold_, rg2_move_threshold_,
|
||||
rg2_still_threshold_, rg3_move_threshold_, rg3_still_threshold_, rg4_move_threshold_, rg4_still_threshold_,
|
||||
rg5_move_threshold_, rg5_still_threshold_, rg6_move_threshold_, rg6_still_threshold_, rg7_move_threshold_,
|
||||
rg7_still_threshold_, rg8_move_threshold_, rg8_still_threshold_;
|
||||
};
|
||||
|
||||
} // namespace ld2410
|
||||
} // namespace esphome
|
55
esphome/components/ld2410/sensor.py
Normal file
55
esphome/components/ld2410/sensor.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
UNIT_CENTIMETER,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
from . import CONF_LD2410_ID, LD2410Component
|
||||
|
||||
DEPENDENCIES = ["ld2410"]
|
||||
CONF_MOVING_DISTANCE = "moving_distance"
|
||||
CONF_STILL_DISTANCE = "still_distance"
|
||||
CONF_MOVING_ENERGY = "moving_energy"
|
||||
CONF_STILL_ENERGY = "still_energy"
|
||||
CONF_DETECTION_DISTANCE = "detection_distance"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT
|
||||
),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if CONF_MOVING_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
|
||||
cg.add(ld2410_component.set_moving_target_distance_sensor(sens))
|
||||
if CONF_STILL_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STILL_DISTANCE])
|
||||
cg.add(ld2410_component.set_still_target_distance_sensor(sens))
|
||||
if CONF_MOVING_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_ENERGY])
|
||||
cg.add(ld2410_component.set_moving_target_energy_sensor(sens))
|
||||
if CONF_STILL_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STILL_ENERGY])
|
||||
cg.add(ld2410_component.set_still_target_energy_sensor(sens))
|
||||
if CONF_DETECTION_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DETECTION_DISTANCE])
|
||||
cg.add(ld2410_component.set_detection_distance_sensor(sens))
|
|
@ -6,17 +6,29 @@
|
|||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-ledc.h>
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/ledc.h>
|
||||
|
||||
#define CLOCK_FREQUENCY 80e6f
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
|
||||
#undef CLOCK_FREQUENCY
|
||||
// starting with ESP32 Arduino 2.0.2, the 40MHz crystal is used as clock by default if supported
|
||||
#define CLOCK_FREQUENCY 40e6f
|
||||
#endif
|
||||
#else
|
||||
#define DEFAULT_CLK LEDC_USE_APB_CLK
|
||||
#endif
|
||||
|
||||
static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5;
|
||||
|
||||
namespace esphome {
|
||||
namespace ledc {
|
||||
|
||||
static const char *const TAG = "ledc.output";
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
|
||||
#ifdef USE_ESP_IDF
|
||||
#if SOC_LEDC_SUPPORT_HS_MODE
|
||||
// Only ESP32 has LEDC_HIGH_SPEED_MODE
|
||||
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
|
||||
|
@ -26,15 +38,13 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H
|
|||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
|
||||
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
|
||||
#endif
|
||||
#else
|
||||
static const int MAX_RES_BITS = 20;
|
||||
#endif
|
||||
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return CLOCK_FREQUENCY / float(1 << bit_depth); }
|
||||
|
||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
|
||||
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
|
||||
return 80e6f / (max_div_num * float(1 << bit_depth));
|
||||
return CLOCK_FREQUENCY / (max_div_num * float(1 << bit_depth));
|
||||
}
|
||||
|
||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
|
@ -50,6 +60,38 @@ optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
|||
return {};
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num,
|
||||
uint8_t channel, uint8_t &bit_depth, float frequency) {
|
||||
bit_depth = *ledc_bit_depth_for_frequency(frequency);
|
||||
if (bit_depth < 1) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
}
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency;
|
||||
timer_conf.clk_cfg = DEFAULT_CLK;
|
||||
|
||||
// Configure the time with fallback in case of error
|
||||
int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX;
|
||||
esp_err_t init_result = ESP_FAIL;
|
||||
while (attempt_count_max > 0 && init_result != ESP_OK) {
|
||||
init_result = ledc_timer_config(&timer_conf);
|
||||
if (init_result != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Unable to initialize timer with frequency %.1f and bit depth of %u", frequency, bit_depth);
|
||||
// try again with a lower bit depth
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(--bit_depth);
|
||||
}
|
||||
attempt_count_max--;
|
||||
}
|
||||
|
||||
return init_result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LEDCOutput::write_state(float state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
|
@ -65,6 +107,7 @@ void LEDCOutput::write_state(float state) {
|
|||
auto duty = static_cast<uint32_t>(duty_rounded);
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
|
||||
ledcWrite(this->channel_, duty);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
|
@ -76,6 +119,7 @@ void LEDCOutput::write_state(float state) {
|
|||
}
|
||||
|
||||
void LEDCOutput::setup() {
|
||||
ESP_LOGV(TAG, "Entering setup...");
|
||||
#ifdef USE_ARDUINO
|
||||
this->update_frequency(this->frequency_);
|
||||
this->turn_off();
|
||||
|
@ -87,19 +131,16 @@ void LEDCOutput::setup() {
|
|||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
|
||||
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
|
||||
if (bit_depth_ < 1) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
|
||||
this->status_set_warning();
|
||||
esp_err_t timer_init_result =
|
||||
configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_);
|
||||
|
||||
if (timer_init_result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth_);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency_;
|
||||
timer_conf.clk_cfg = LEDC_AUTO_CLK;
|
||||
ledc_timer_config(&timer_conf);
|
||||
ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_);
|
||||
|
||||
ledc_channel_config_t chan_conf{};
|
||||
chan_conf.gpio_num = pin_->get_pin();
|
||||
|
@ -111,6 +152,7 @@ void LEDCOutput::setup() {
|
|||
chan_conf.hpoint = 0;
|
||||
ledc_channel_config(&chan_conf);
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -118,36 +160,80 @@ void LEDCOutput::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, "LEDC Output:");
|
||||
LOG_PIN(" Pin ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_);
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth-1: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth+1: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS);
|
||||
ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY);
|
||||
}
|
||||
|
||||
void LEDCOutput::update_frequency(float frequency) {
|
||||
auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency);
|
||||
if (!bit_depth_opt.has_value()) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", this->frequency_);
|
||||
this->status_set_error();
|
||||
}
|
||||
this->bit_depth_ = bit_depth_opt.value_or(8);
|
||||
this->frequency_ = frequency;
|
||||
#ifdef USE_ARDUINO
|
||||
ledcSetup(this->channel_, frequency, this->bit_depth_);
|
||||
initialized_ = true;
|
||||
ESP_LOGV(TAG, "Using Arduino API - Trying to define channel, frequency and bit depth...");
|
||||
u_int32_t configured_frequency = 0;
|
||||
|
||||
// Configure LEDC channel, frequency and bit depth with fallback
|
||||
int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX;
|
||||
while (attempt_count_max > 0 && configured_frequency == 0) {
|
||||
ESP_LOGV(TAG, "Trying initialize channel %u with frequency %.1f and bit depth of %u...", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
configured_frequency = ledcSetup(this->channel_, frequency, this->bit_depth_);
|
||||
if (configured_frequency != 0) {
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
ESP_LOGV(TAG, "Configured frequency: %u with bit depth: %u", configured_frequency, this->bit_depth_);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unable to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
// try again with a lower bit depth
|
||||
this->bit_depth_--;
|
||||
}
|
||||
attempt_count_max--;
|
||||
}
|
||||
|
||||
if (configured_frequency == 0) {
|
||||
ESP_LOGE(TAG, "Permanently failed to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth_);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency_;
|
||||
timer_conf.clk_cfg = LEDC_AUTO_CLK;
|
||||
ledc_timer_config(&timer_conf);
|
||||
esp_err_t timer_init_result =
|
||||
configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_);
|
||||
|
||||
if (timer_init_result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_error();
|
||||
#endif
|
||||
// re-apply duty
|
||||
this->write_state(this->duty_);
|
||||
|
|
|
@ -276,7 +276,15 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
|
|||
void LightState::save_remote_values_() {
|
||||
LightStateRTCState saved;
|
||||
saved.color_mode = this->remote_values.get_color_mode();
|
||||
saved.state = this->remote_values.is_on();
|
||||
switch (this->restore_mode_) {
|
||||
case LIGHT_RESTORE_AND_OFF:
|
||||
case LIGHT_RESTORE_AND_ON:
|
||||
saved.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
|
||||
break;
|
||||
default:
|
||||
saved.state = this->remote_values.is_on();
|
||||
break;
|
||||
}
|
||||
saved.brightness = this->remote_values.get_brightness();
|
||||
saved.color_brightness = this->remote_values.get_color_brightness();
|
||||
saved.red = this->remote_values.get_red();
|
||||
|
|
71
esphome/components/matrix_keypad/__init__.py
Normal file
71
esphome/components/matrix_keypad/__init__.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import key_provider
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
AUTO_LOAD = ["key_provider"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
matrix_keypad_ns = cg.esphome_ns.namespace("matrix_keypad")
|
||||
MatrixKeypad = matrix_keypad_ns.class_(
|
||||
"MatrixKeypad", key_provider.KeyProvider, cg.Component
|
||||
)
|
||||
|
||||
CONF_KEYPAD_ID = "keypad_id"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_COLUMNS = "columns"
|
||||
CONF_KEYS = "keys"
|
||||
CONF_DEBOUNCE_TIME = "debounce_time"
|
||||
CONF_HAS_DIODES = "has_diodes"
|
||||
|
||||
|
||||
def check_keys(obj):
|
||||
if CONF_KEYS in obj:
|
||||
if len(obj[CONF_KEYS]) != len(obj[CONF_ROWS]) * len(obj[CONF_COLUMNS]):
|
||||
raise cv.Invalid("The number of key codes must equal the number of buttons")
|
||||
return obj
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MatrixKeypad),
|
||||
cv.Required(CONF_ROWS): cv.All(
|
||||
cv.ensure_list({cv.Required(CONF_PIN): pins.gpio_output_pin_schema}),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Required(CONF_COLUMNS): cv.All(
|
||||
cv.ensure_list({cv.Required(CONF_PIN): pins.gpio_input_pin_schema}),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_KEYS): cv.string,
|
||||
cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100),
|
||||
cv.Optional(CONF_HAS_DIODES): cv.boolean,
|
||||
}
|
||||
),
|
||||
check_keys,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
row_pins = []
|
||||
for conf in config[CONF_ROWS]:
|
||||
pin = await cg.gpio_pin_expression(conf[CONF_PIN])
|
||||
row_pins.append(pin)
|
||||
cg.add(var.set_rows(row_pins))
|
||||
col_pins = []
|
||||
for conf in config[CONF_COLUMNS]:
|
||||
pin = await cg.gpio_pin_expression(conf[CONF_PIN])
|
||||
col_pins.append(pin)
|
||||
cg.add(var.set_columns(col_pins))
|
||||
if CONF_KEYS in config:
|
||||
cg.add(var.set_keys(config[CONF_KEYS]))
|
||||
cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME]))
|
||||
if CONF_HAS_DIODES in config:
|
||||
cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
|
53
esphome/components/matrix_keypad/binary_sensor/__init__.py
Normal file
53
esphome/components/matrix_keypad/binary_sensor/__init__.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID, CONF_KEY
|
||||
from .. import MatrixKeypad, matrix_keypad_ns, CONF_KEYPAD_ID
|
||||
|
||||
CONF_ROW = "row"
|
||||
CONF_COL = "col"
|
||||
|
||||
DEPENDENCIES = ["matrix_keypad"]
|
||||
|
||||
MatrixKeypadBinarySensor = matrix_keypad_ns.class_(
|
||||
"MatrixKeypadBinarySensor", binary_sensor.BinarySensor
|
||||
)
|
||||
|
||||
|
||||
def check_button(obj):
|
||||
if CONF_ROW in obj or CONF_COL in obj:
|
||||
if CONF_KEY in obj:
|
||||
raise cv.Invalid("You can't provide both a key and a position")
|
||||
if CONF_ROW not in obj:
|
||||
raise cv.Invalid("Missing row")
|
||||
if CONF_COL not in obj:
|
||||
raise cv.Invalid("Missing col")
|
||||
elif CONF_KEY not in obj:
|
||||
raise cv.Invalid("Missing key or position")
|
||||
elif len(obj[CONF_KEY]) != 1:
|
||||
raise cv.Invalid("Key must be one character")
|
||||
return obj
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MatrixKeypadBinarySensor),
|
||||
cv.GenerateID(CONF_KEYPAD_ID): cv.use_id(MatrixKeypad),
|
||||
cv.Optional(CONF_ROW): cv.int_,
|
||||
cv.Optional(CONF_COL): cv.int_,
|
||||
cv.Optional(CONF_KEY): cv.string,
|
||||
}
|
||||
),
|
||||
check_button,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if CONF_KEY in config:
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_KEY][0])
|
||||
else:
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_ROW], config[CONF_COL])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
matrix_keypad = await cg.get_variable(config[CONF_KEYPAD_ID])
|
||||
cg.add(matrix_keypad.register_listener(var))
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue