mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
263 Commits
v1.20.1
...
2021.9.0b3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0da97289e6 | ||
![]() |
91f12a50cf | ||
![]() |
e92a9d1d9e | ||
![]() |
4eb51ab4d6 | ||
![]() |
f1a8d957f8 | ||
![]() |
9821a3442b | ||
![]() |
87842e097b | ||
![]() |
7dd40e2014 | ||
![]() |
a719998220 | ||
![]() |
09a6fdf1c7 | ||
![]() |
d2616cbdfc | ||
![]() |
faf1c8bee8 | ||
![]() |
f09aca4865 | ||
![]() |
cc52f37933 | ||
![]() |
e5051eefbc | ||
![]() |
9e5cd0da51 | ||
![]() |
4e120a291e | ||
![]() |
2790d72bff | ||
![]() |
e44f447d85 | ||
![]() |
4356581db0 | ||
![]() |
f87a701b28 | ||
![]() |
fa2eb46cd6 | ||
![]() |
f924e80f43 | ||
![]() |
6180ee8065 | ||
![]() |
1be106c0b5 | ||
![]() |
b0533db2eb | ||
![]() |
dba502c756 | ||
![]() |
d9cb64b893 | ||
![]() |
2d91e6b977 | ||
![]() |
4a6f1f150a | ||
![]() |
7f76f3726f | ||
![]() |
e2d97b6f36 | ||
![]() |
2a653642f5 | ||
![]() |
97eba1eecc | ||
![]() |
ff6bed54c6 | ||
![]() |
f9b0666adf | ||
![]() |
ca12b8aa56 | ||
![]() |
77508f7e44 | ||
![]() |
54de0ca0da | ||
![]() |
f364788c03 | ||
![]() |
00aaf84c37 | ||
![]() |
b01bc76dc5 | ||
![]() |
910f812737 | ||
![]() |
a4d024f43d | ||
![]() |
9937ad7fa0 | ||
![]() |
edcd88123d | ||
![]() |
ea1b5e19f0 | ||
![]() |
54337befc2 | ||
![]() |
140ef791aa | ||
![]() |
58350b6c99 | ||
![]() |
f186ff8b46 | ||
![]() |
03190611bb | ||
![]() |
37f322585e | ||
![]() |
9218e85bd6 | ||
![]() |
f923ba87c0 | ||
![]() |
fac49896df | ||
![]() |
56225701f9 | ||
![]() |
b5de43b225 | ||
![]() |
b955527f6c | ||
![]() |
94b28102f5 | ||
![]() |
de871862a8 | ||
![]() |
3be56fd502 | ||
![]() |
39cd2838df | ||
![]() |
565473c90c | ||
![]() |
ed68a0e773 | ||
![]() |
e2640c8368 | ||
![]() |
eff626248f | ||
![]() |
ce29a3b07a | ||
![]() |
1b89174558 | ||
![]() |
1c1ad32610 | ||
![]() |
71237e2f76 | ||
![]() |
518c271eba | ||
![]() |
d71996e58d | ||
![]() |
2f33cd2db5 | ||
![]() |
5ec9bb0fb5 | ||
![]() |
8cc3cbb22e | ||
![]() |
b0fa317302 | ||
![]() |
5cb56bc677 | ||
![]() |
21f8fd9fa5 | ||
![]() |
2100ef63a9 | ||
![]() |
29db77c9c9 | ||
![]() |
2e59ad90cc | ||
![]() |
1b8c9edcde | ||
![]() |
d4c2a85f9c | ||
![]() |
8c75b87e94 | ||
![]() |
409d4b9d47 | ||
![]() |
4e3b95d120 | ||
![]() |
61a9c9fa33 | ||
![]() |
9c605f2d46 | ||
![]() |
44bb5a89c8 | ||
![]() |
cbdb96f105 | ||
![]() |
9ee3463d07 | ||
![]() |
f0b14055b6 | ||
![]() |
fbd9e87b51 | ||
![]() |
edb3b77916 | ||
![]() |
ebaa84617f | ||
![]() |
8eb18995cb | ||
![]() |
ebabf0e7d8 | ||
![]() |
607e1f823d | ||
![]() |
3b52a306cd | ||
![]() |
0c370d5897 | ||
![]() |
8bf0448f41 | ||
![]() |
14e04eb231 | ||
![]() |
1be9bac3a9 | ||
![]() |
02b5a3efb8 | ||
![]() |
bd457f64d8 | ||
![]() |
9efeea14f2 | ||
![]() |
9b48ff5775 | ||
![]() |
117b58ebe6 | ||
![]() |
303b699005 | ||
![]() |
9173da0416 | ||
![]() |
d2cd65f5db | ||
![]() |
2735f96516 | ||
![]() |
6847645782 | ||
![]() |
b0bc898278 | ||
![]() |
c0f6af7213 | ||
![]() |
5edebaf468 | ||
![]() |
d436409153 | ||
![]() |
8c41fc2b1d | ||
![]() |
46f17bea66 | ||
![]() |
11477dbc03 | ||
![]() |
947c104eff | ||
![]() |
e5366dbbe7 | ||
![]() |
d3375193a9 | ||
![]() |
6144ce1fe0 | ||
![]() |
1771e673d2 | ||
![]() |
d258e06fd7 | ||
![]() |
854f4a8896 | ||
![]() |
f94c221a9a | ||
![]() |
6a2f0f5143 | ||
![]() |
183e2a8471 | ||
![]() |
c6c2842bdb | ||
![]() |
f26767b65e | ||
![]() |
98d32876b5 | ||
![]() |
e5d0f3c036 | ||
![]() |
cc15aaacbb | ||
![]() |
553df1d57b | ||
![]() |
b92311402a | ||
![]() |
93796491af | ||
![]() |
c038cf27a7 | ||
![]() |
1f42d32eb5 | ||
![]() |
06bde559da | ||
![]() |
922f7167f5 | ||
![]() |
90c0d3e12f | ||
![]() |
bf5f846fc6 | ||
![]() |
926bcc71ae | ||
![]() |
ea4a458214 | ||
![]() |
b3ae3e1feb | ||
![]() |
fe7af21c91 | ||
![]() |
29f72037fe | ||
![]() |
1d6b4bfcef | ||
![]() |
5bfac5ec09 | ||
![]() |
dfffaace26 | ||
![]() |
1d5f628c7a | ||
![]() |
cb8a6f66fa | ||
![]() |
cb21c7c18d | ||
![]() |
0d104776bc | ||
![]() |
5f27757039 | ||
![]() |
532907219b | ||
![]() |
eeaba74553 | ||
![]() |
dd637582a4 | ||
![]() |
b0d12aeea1 | ||
![]() |
bdbd813455 | ||
![]() |
a6fac2b175 | ||
![]() |
5ce923ea90 | ||
![]() |
29f0508dc2 | ||
![]() |
3ffa59f0cd | ||
![]() |
790d6ef94c | ||
![]() |
7828f48b9a | ||
![]() |
768c71830b | ||
![]() |
ceb0564ebf | ||
![]() |
20f7eb7327 | ||
![]() |
441d5bd44d | ||
![]() |
9fa19df2ff | ||
![]() |
39f64f597e | ||
![]() |
160429eb24 | ||
![]() |
6516c64e67 | ||
![]() |
4c8a703084 | ||
![]() |
335210d788 | ||
![]() |
9b04e657db | ||
![]() |
f7311aa025 | ||
![]() |
fb24e55c8d | ||
![]() |
b58ca46a46 | ||
![]() |
76991cdcc4 | ||
![]() |
69c7cf783e | ||
![]() |
f751c3828e | ||
![]() |
5c65f9f9ad | ||
![]() |
81ae6709e4 | ||
![]() |
593a3d48fb | ||
![]() |
a8b90283d8 | ||
![]() |
80076f935d | ||
![]() |
9fbb3659a6 | ||
![]() |
fee446c28a | ||
![]() |
1d56f0b035 | ||
![]() |
34e8979d40 | ||
![]() |
2966a62429 | ||
![]() |
5983ccc55c | ||
![]() |
de382b704c | ||
![]() |
16dbbfabc6 | ||
![]() |
af8d04818d | ||
![]() |
ee19ef1aac | ||
![]() |
5e2d4e332a | ||
![]() |
c6c857dfff | ||
![]() |
8dbac20f8b | ||
![]() |
341fddb9aa | ||
![]() |
456824669f | ||
![]() |
62f3039d82 | ||
![]() |
be4c718859 | ||
![]() |
c2f9ed7c59 | ||
![]() |
bfac6607d1 | ||
![]() |
513066ba52 | ||
![]() |
316777f757 | ||
![]() |
246950159d | ||
![]() |
31d6a54b06 | ||
![]() |
5c3a6164bb | ||
![]() |
1652914d39 | ||
![]() |
618cfd9ec5 | ||
![]() |
f97cfe9916 | ||
![]() |
b9259a0238 | ||
![]() |
5abbe385c5 | ||
![]() |
b0a3891498 | ||
![]() |
2a9e3d84fd | ||
![]() |
a3dcac62f9 | ||
![]() |
6b535b11f8 | ||
![]() |
d9f09a7523 | ||
![]() |
159744e09e | ||
![]() |
c2637a76f7 | ||
![]() |
237edd75d1 | ||
![]() |
a34d5e3901 | ||
![]() |
1dd43a75f2 | ||
![]() |
1f5cbca509 | ||
![]() |
3749c11f21 | ||
![]() |
66cdb761dc | ||
![]() |
f0d9ad6a4e | ||
![]() |
03e317d052 | ||
![]() |
ba461e51a8 | ||
![]() |
80949521b6 | ||
![]() |
acbb8e9fd0 | ||
![]() |
90394a50df | ||
![]() |
5379794f16 | ||
![]() |
0a32321c85 | ||
![]() |
c9062599df | ||
![]() |
fc42f14448 | ||
![]() |
3e65e6c69a | ||
![]() |
3b3297d269 | ||
![]() |
fc0deb642a | ||
![]() |
9f2b2f51ff | ||
![]() |
01a4b4e82f | ||
![]() |
766866197b | ||
![]() |
9b5a3cbcd3 | ||
![]() |
d2ed3b9bec | ||
![]() |
99d2db42cd | ||
![]() |
7619507e6c | ||
![]() |
71d9d64a02 | ||
![]() |
2e49fd7b48 | ||
![]() |
06912b492f | ||
![]() |
442e58b07a | ||
![]() |
799f04efc0 | ||
![]() |
cc7dbeada6 | ||
![]() |
45d368e3a1 | ||
![]() |
628a94bad3 | ||
![]() |
0651716b96 | ||
![]() |
0992609bf4 |
@@ -14,7 +14,12 @@ Checks: >-
|
||||
-cert-str34-c,
|
||||
-clang-analyzer-optin.cplusplus.UninitializedObject,
|
||||
-clang-analyzer-osx.*,
|
||||
-clang-diagnostic-delete-abstract-non-virtual-dtor,
|
||||
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
|
||||
-clang-diagnostic-shadow-field,
|
||||
-clang-diagnostic-sign-compare,
|
||||
-clang-diagnostic-unused-variable,
|
||||
-clang-diagnostic-unused-const-variable,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
@@ -80,7 +85,6 @@ Checks: >-
|
||||
-readability-use-anyofallof,
|
||||
-warnings-as-errors
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '^.*/src/esphome/.*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: google
|
||||
CheckOptions:
|
||||
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -36,26 +36,27 @@ jobs:
|
||||
container: ghcr.io/esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
|
||||
# Also run git-diff-index so that the step is marked as failed on formatting errors,
|
||||
# since clang-format doesn't do anything but change files if -i is passed.
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
run: |
|
||||
script/clang-format -i
|
||||
git diff-index --quiet HEAD --
|
||||
if: ${{ matrix.id == 'clang-format' }}
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
if: ${{ matrix.id == 'clang-tidy' }}
|
||||
|
||||
- name: Suggest changes
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
ci:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
@@ -82,6 +83,9 @@ jobs:
|
||||
- id: test
|
||||
file: tests/test4.yaml
|
||||
name: Test tests/test4.yaml
|
||||
- id: test
|
||||
file: tests/test5.yaml
|
||||
name: Test tests/test5.yaml
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
id: tag
|
||||
run: |
|
||||
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
else
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -125,4 +125,5 @@ config/
|
||||
tests/build/
|
||||
tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
/.temp/
|
||||
.pio/
|
||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"label": "clang-tidy",
|
||||
"type": "shell",
|
||||
"command": "test -f .gcc-flags.json || pio init --silent --ide atom; ./script/clang-tidy",
|
||||
"command": "./script/clang-tidy",
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "clang-tidy",
|
||||
|
29
CODEOWNERS
29
CODEOWNERS
@@ -14,6 +14,10 @@ esphome/core/* @esphome/core
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
esphome/components/api/* @OttoWinter
|
||||
@@ -27,8 +31,10 @@ esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
@@ -37,6 +43,7 @@ esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
@@ -48,7 +55,11 @@ esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/improv/* @jesserockz
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
@@ -69,8 +80,7 @@ esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
@@ -83,25 +93,34 @@ esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
@@ -116,17 +135,23 @@ esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tlc5947/* @rnauber
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
|
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files installs the user ESPHome version if specified
|
||||
# ==============================================================================
|
||||
|
||||
declare esphome_version
|
||||
|
||||
if bashio::config.has_value 'esphome_version'; then
|
||||
esphome_version=$(bashio::config 'esphome_version')
|
||||
if [[ $esphome_version == *":"* ]]; then
|
||||
IFS=':' read -r -a array <<< "$esphome_version"
|
||||
username=${array[0]}
|
||||
ref=${array[1]}
|
||||
else
|
||||
username="esphome"
|
||||
ref=$esphome_version
|
||||
fi
|
||||
full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
|
||||
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||
pip3 install -U --no-cache-dir "${full_url}" \
|
||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||
fi
|
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files migrates the esphome config directory from the old path
|
||||
# ==============================================================================
|
||||
|
||||
if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
|
||||
echo "Moving config directory from /config/esphomeyaml to /config/esphome"
|
||||
mv /config/esphomeyaml /config/esphome
|
||||
mv /config/esphome/.esphomeyaml /config/esphome/.esphome
|
||||
fi
|
@@ -11,6 +11,7 @@ from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BROKER,
|
||||
CONF_DEASSERT_RTS_DTR,
|
||||
CONF_LOGGER,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
@@ -99,10 +100,21 @@ def run_miniterm(config, port):
|
||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
return
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
backtrace_state = False
|
||||
with serial.Serial(port, baudrate=baud_rate) as ser:
|
||||
ser = serial.Serial()
|
||||
ser.baudrate = baud_rate
|
||||
ser.port = port
|
||||
|
||||
# We can't set to False by default since it leads to toggling and hence
|
||||
# ESP32 resets on some platforms.
|
||||
if config["logger"][CONF_DEASSERT_RTS_DTR]:
|
||||
ser.dtr = False
|
||||
ser.rts = False
|
||||
|
||||
with ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
@@ -284,7 +296,6 @@ def command_vscode(args):
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
CORE.config_path = args.configuration
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
@@ -394,7 +405,7 @@ def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -408,7 +419,7 @@ def command_update_all(args):
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||
@@ -505,6 +516,7 @@ def parse_args(argv):
|
||||
"clean",
|
||||
"dashboard",
|
||||
"vscode",
|
||||
"update-all",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -681,14 +693,12 @@ def parse_args(argv):
|
||||
)
|
||||
|
||||
parser_vscode = subparsers.add_parser("vscode")
|
||||
parser_vscode.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
|
||||
parser_vscode.add_argument("--ace", action="store_true")
|
||||
|
||||
parser_update = subparsers.add_parser("update-all")
|
||||
parser_update.add_argument(
|
||||
"configuration", help="Your YAML configuration file directory.", nargs=1
|
||||
"configuration", help="Your YAML configuration file directories.", nargs="+"
|
||||
)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
1152
esphome/boards.py
Normal file
1152
esphome/boards.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -125,7 +125,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
// Attaching pin interrupts on the same pin will override the previous interupt
|
||||
// Attaching pin interrupts on the same pin will override the previous interrupt
|
||||
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
|
||||
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
|
||||
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
|
||||
|
@@ -42,8 +42,9 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
||||
|
||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||
for (int led = it.size(); led-- > 0;) {
|
||||
it[led].set(COLOR_BLACK);
|
||||
it[led].set(Color::BLACK);
|
||||
}
|
||||
it.schedule_show();
|
||||
}
|
||||
|
||||
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
|
||||
@@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
|
||||
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
||||
}
|
||||
|
||||
it.schedule_show();
|
||||
return CONSUMED;
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@ static const char *const TAG = "adc";
|
||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
|
||||
|
||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
switch (pin) {
|
||||
case 36:
|
||||
return ADC1_CHANNEL_0;
|
||||
@@ -34,6 +35,22 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
default:
|
||||
return ADC1_CHANNEL_MAX;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
|
||||
switch (pin) {
|
||||
case 0:
|
||||
return ADC1_CHANNEL_0;
|
||||
case 1:
|
||||
return ADC1_CHANNEL_1;
|
||||
case 2:
|
||||
return ADC1_CHANNEL_2;
|
||||
case 3:
|
||||
return ADC1_CHANNEL_3;
|
||||
case 4:
|
||||
return ADC1_CHANNEL_4;
|
||||
default:
|
||||
return ADC1_CHANNEL_MAX;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -46,8 +63,10 @@ void ADCSensor::setup() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
@@ -89,6 +108,7 @@ float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
int raw = adc1_get_raw(gpio_to_adc1(pin_));
|
||||
float value_v = raw / 4095.0f;
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
value_v *= 1.1;
|
||||
@@ -105,6 +125,24 @@ float ADCSensor::sample() {
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
value_v *= 0.84;
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
value_v *= 1.13;
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
value_v *= 1.56;
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
value_v *= 3.0;
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return value_v;
|
||||
#endif
|
||||
|
||||
|
@@ -7,7 +7,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
@@ -37,7 +36,10 @@ ADCSensor = adc_ns.class_(
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
|
@@ -8,7 +8,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
@@ -32,27 +31,34 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -5,7 +5,6 @@ from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_MULTIPLEXER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
CONF_ID,
|
||||
@@ -53,7 +52,10 @@ ADS1115Sensor = ads1115_ns.class_(
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
|
@@ -10,7 +10,7 @@
|
||||
//
|
||||
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
|
||||
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
|
||||
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
|
||||
// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
|
||||
|
||||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -23,7 +23,7 @@ static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
|
||||
void AHT10Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
|
||||
@@ -58,8 +58,8 @@ void AHT10Component::update() {
|
||||
uint8_t delay = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay = AHT10_HUMIDITY_DELAY;
|
||||
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis());
|
||||
delay_microseconds_accurate(4);
|
||||
if (!this->read_bytes(0, data, 6, delay)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
|
@@ -7,7 +7,6 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -23,18 +22,16 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
23
esphome/components/airthings_ble/__init__.py
Normal file
23
esphome/components/airthings_ble/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jeromelaban"]
|
||||
|
||||
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
|
||||
AirthingsListener = airthings_ble_ns.class_(
|
||||
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirthingsListener),
|
||||
}
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
33
esphome/components/airthings_ble/airthings_listener.cpp
Normal file
33
esphome/components/airthings_ble/airthings_listener.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "airthings_listener.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_ble {
|
||||
|
||||
static const char *TAG = "airthings_ble";
|
||||
|
||||
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
for (auto &it : device.get_manufacturer_datas()) {
|
||||
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
|
||||
if (it.data.size() < 4)
|
||||
continue;
|
||||
|
||||
uint32_t sn = it.data[0];
|
||||
sn |= ((uint32_t) it.data[1] << 8);
|
||||
sn |= ((uint32_t) it.data[2] << 16);
|
||||
sn |= ((uint32_t) it.data[3] << 24);
|
||||
|
||||
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace airthings_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
20
esphome/components/airthings_ble/airthings_listener.h
Normal file
20
esphome/components/airthings_ble/airthings_listener.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include <BLEDevice.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_ble {
|
||||
|
||||
class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
};
|
||||
|
||||
} // namespace airthings_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
1
esphome/components/airthings_wave_plus/__init__.py
Normal file
1
esphome/components/airthings_wave_plus/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@jeromelaban"]
|
142
esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
Normal file
142
esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "airthings_wave_plus.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
||||
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "Connected successfully!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "Disconnected!");
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto value = (WavePlusReadings *) raw_value;
|
||||
|
||||
if (sizeof(WavePlusReadings) <= value_len) {
|
||||
ESP_LOGD(TAG, "version = %d", value->version);
|
||||
|
||||
if (value->version == 1) {
|
||||
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
|
||||
|
||||
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
|
||||
if (is_valid_radon_value_(value->radon)) {
|
||||
this->radon_sensor_->publish_state(value->radon);
|
||||
}
|
||||
if (is_valid_radon_value_(value->radon_lt)) {
|
||||
this->radon_long_term_sensor_->publish_state(value->radon_lt);
|
||||
}
|
||||
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
|
||||
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
|
||||
if (is_valid_co2_value_(value->co2)) {
|
||||
this->co2_sensor_->publish_state(value->co2);
|
||||
}
|
||||
if (is_valid_voc_value_(value->voc)) {
|
||||
this->tvoc_sensor_->publish_state(value->voc);
|
||||
}
|
||||
|
||||
// This instance must not stay connected
|
||||
// so other clients can connect to it (e.g. the
|
||||
// mobile app).
|
||||
parent()->set_enabled(false);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; }
|
||||
|
||||
bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; }
|
||||
|
||||
bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; }
|
||||
|
||||
void AirthingsWavePlus::loop() {}
|
||||
|
||||
void AirthingsWavePlus::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
parent()->connect();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection in progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::dump_config() {
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
|
||||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
}
|
||||
|
||||
AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) {
|
||||
auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative();
|
||||
auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative();
|
||||
|
||||
service_uuid = espbt::ESPBTUUID::from_uuid(service_bt);
|
||||
sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt);
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::setup() {}
|
||||
|
||||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
79
esphome/components/airthings_wave_plus/airthings_wave_plus.h
Normal file
79
esphome/components/airthings_wave_plus/airthings_wave_plus.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
#include <BLEDevice.h>
|
||||
|
||||
using namespace esphome::ble_client;
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
||||
static const char *TAG = "airthings_wave_plus";
|
||||
|
||||
class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
AirthingsWavePlus();
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
||||
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
|
||||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
|
||||
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
|
||||
protected:
|
||||
bool is_valid_radon_value_(short radon);
|
||||
bool is_valid_voc_value_(short voc);
|
||||
bool is_valid_co2_value_(short co2);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *radon_sensor_{nullptr};
|
||||
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
|
||||
uint16_t handle;
|
||||
espbt::ESPBTUUID service_uuid;
|
||||
espbt::ESPBTUUID sensors_data_characteristic_uuid;
|
||||
|
||||
struct WavePlusReadings {
|
||||
uint8_t version;
|
||||
uint8_t humidity;
|
||||
uint8_t ambientLight;
|
||||
uint8_t unused01;
|
||||
uint16_t radon;
|
||||
uint16_t radon_lt;
|
||||
uint16_t temperature;
|
||||
uint16_t pressure;
|
||||
uint16_t co2;
|
||||
uint16_t voc;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
116
esphome/components/airthings_wave_plus/sensor.py
Normal file
116
esphome/components/airthings_wave_plus/sensor.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_RADIOACTIVE,
|
||||
CONF_ID,
|
||||
CONF_RADON,
|
||||
CONF_RADON_LONG_TERM,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TVOC,
|
||||
CONF_CO2,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
|
||||
AirthingsWavePlus = airthings_wave_plus_ns.class_(
|
||||
"AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=0,
|
||||
),
|
||||
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5mins"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_RADON in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON])
|
||||
cg.add(var.set_radon(sens))
|
||||
if CONF_RADON_LONG_TERM in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
|
||||
cg.add(var.set_radon_long_term(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure(sens))
|
||||
if CONF_CO2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
||||
cg.add(var.set_co2(sens))
|
||||
if CONF_TVOC in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
@@ -25,18 +24,16 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
0
esphome/components/am43/__init__.py
Normal file
0
esphome/components/am43/__init__.py
Normal file
115
esphome/components/am43/am43.cpp
Normal file
115
esphome/components/am43/am43.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "am43.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
static const char *TAG = "am43";
|
||||
|
||||
void Am43::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AM43");
|
||||
LOG_SENSOR(" ", "Battery", this->battery_);
|
||||
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
|
||||
}
|
||||
|
||||
void Am43::setup() {
|
||||
this->encoder_ = new Am43Encoder();
|
||||
this->decoder_ = new Am43Decoder();
|
||||
this->logged_in_ = false;
|
||||
this->last_battery_update_ = 0;
|
||||
this->current_sensor_ = 0;
|
||||
}
|
||||
|
||||
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
this->logged_in_ = false;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->logged_in_ = false;
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
if (this->battery_ != nullptr)
|
||||
this->battery_->publish_state(NAN);
|
||||
if (this->illuminance_ != nullptr)
|
||||
this->illuminance_->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
|
||||
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
|
||||
this->parent_->address_str().c_str());
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
|
||||
this->parent_->address_str().c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->update();
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.handle != this->char_handle_)
|
||||
break;
|
||||
this->decoder_->decode(param->notify.value, param->notify.value_len);
|
||||
|
||||
if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
|
||||
millis() - this->last_battery_update_ > 10000) {
|
||||
this->battery_->publish_state(this->decoder_->battery_level_);
|
||||
this->last_battery_update_ = millis();
|
||||
}
|
||||
|
||||
if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
|
||||
this->illuminance_->publish_state(this->decoder_->light_level_);
|
||||
}
|
||||
|
||||
if (this->current_sensor_ > 0) {
|
||||
if (this->illuminance_ != nullptr) {
|
||||
auto packet = this->encoder_->get_light_level_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
}
|
||||
this->current_sensor_ = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Am43::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
|
||||
return;
|
||||
}
|
||||
if (this->current_sensor_ == 0) {
|
||||
if (this->battery_ != nullptr) {
|
||||
auto packet = this->encoder_->get_battery_level_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
this->current_sensor_++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
45
esphome/components/am43/am43.h
Normal file
45
esphome/components/am43/am43.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/am43/am43_base.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
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 dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_battery(sensor::Sensor *battery) { battery_ = battery; }
|
||||
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
|
||||
|
||||
protected:
|
||||
uint16_t char_handle_;
|
||||
Am43Encoder *encoder_;
|
||||
Am43Decoder *decoder_;
|
||||
bool logged_in_;
|
||||
sensor::Sensor *battery_{nullptr};
|
||||
sensor::Sensor *illuminance_{nullptr};
|
||||
uint8_t current_sensor_;
|
||||
// The AM43 often gets into a state where it spams loads of battery update
|
||||
// notifications. Here we will limit to no more than every 10s.
|
||||
uint8_t last_battery_update_;
|
||||
};
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
142
esphome/components/am43/am43_base.cpp
Normal file
142
esphome/components/am43/am43_base.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "am43_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
|
||||
|
||||
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
|
||||
char buf[64];
|
||||
memset(buf, 0, 64);
|
||||
for (int i = 0; i < len; i++)
|
||||
sprintf(&buf[i * 2], "%02x", data[i]);
|
||||
std::string ret = buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_battery_level_request() {
|
||||
uint8_t data = 0x1;
|
||||
return this->encode_(0xA2, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_light_level_request() {
|
||||
uint8_t data = 0x1;
|
||||
return this->encode_(0xAA, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_position_request() {
|
||||
uint8_t data = 0x1;
|
||||
return this->encode_(CMD_GET_POSITION, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
|
||||
uint8_t data[2];
|
||||
data[0] = (pin & 0xFF00) >> 8;
|
||||
data[1] = pin & 0xFF;
|
||||
return this->encode_(CMD_SEND_PIN, data, 2);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_open_request() {
|
||||
uint8_t data = 0xDD;
|
||||
return this->encode_(CMD_SET_STATE, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_close_request() {
|
||||
uint8_t data = 0xEE;
|
||||
return this->encode_(CMD_SET_STATE, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_stop_request() {
|
||||
uint8_t data = 0xCC;
|
||||
return this->encode_(CMD_SET_STATE, &data, 1);
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
|
||||
return this->encode_(CMD_SET_POSITION, &position, 1);
|
||||
}
|
||||
|
||||
void Am43Encoder::checksum_() {
|
||||
uint8_t checksum = 0;
|
||||
int i = 0;
|
||||
for (i = 0; i < this->packet_.length; i++)
|
||||
checksum = checksum ^ this->packet_.data[i];
|
||||
this->packet_.data[i] = checksum ^ 0xff;
|
||||
this->packet_.length++;
|
||||
}
|
||||
|
||||
Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
|
||||
memcpy(this->packet_.data, START_PACKET, 5);
|
||||
this->packet_.data[5] = command;
|
||||
this->packet_.data[6] = length;
|
||||
memcpy(&this->packet_.data[7], data, length);
|
||||
this->packet_.length = length + 7;
|
||||
this->checksum_();
|
||||
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
|
||||
return &this->packet_;
|
||||
}
|
||||
|
||||
#define VERIFY_MIN_LENGTH(x) \
|
||||
if (length < (x)) \
|
||||
return;
|
||||
|
||||
void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
|
||||
this->has_battery_level_ = false;
|
||||
this->has_light_level_ = false;
|
||||
this->has_set_position_response_ = false;
|
||||
this->has_set_state_response_ = false;
|
||||
this->has_position_ = false;
|
||||
this->has_pin_response_ = false;
|
||||
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
|
||||
|
||||
if (length < 2 || data[0] != 0x9a)
|
||||
return;
|
||||
switch (data[1]) {
|
||||
case CMD_GET_BATTERY_LEVEL: {
|
||||
VERIFY_MIN_LENGTH(8);
|
||||
this->battery_level_ = data[7];
|
||||
this->has_battery_level_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_GET_LIGHT_LEVEL: {
|
||||
VERIFY_MIN_LENGTH(5);
|
||||
this->light_level_ = 100 * ((float) data[4] / 9);
|
||||
this->has_light_level_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_GET_POSITION: {
|
||||
VERIFY_MIN_LENGTH(6);
|
||||
this->position_ = data[5];
|
||||
this->has_position_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_NOTIFY_POSITION: {
|
||||
VERIFY_MIN_LENGTH(5);
|
||||
this->position_ = data[4];
|
||||
this->has_position_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_SEND_PIN: {
|
||||
VERIFY_MIN_LENGTH(4);
|
||||
this->pin_ok_ = data[3] == RESPONSE_ACK;
|
||||
this->has_pin_response_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_SET_POSITION: {
|
||||
VERIFY_MIN_LENGTH(4);
|
||||
this->set_position_ok_ = data[3] == RESPONSE_ACK;
|
||||
this->has_set_position_response_ = true;
|
||||
break;
|
||||
}
|
||||
case CMD_SET_STATE: {
|
||||
VERIFY_MIN_LENGTH(4);
|
||||
this->set_state_ok_ = data[3] == RESPONSE_ACK;
|
||||
this->has_set_state_response_ = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
78
esphome/components/am43/am43_base.h
Normal file
78
esphome/components/am43/am43_base.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
static const uint16_t AM43_SERVICE_UUID = 0xFE50;
|
||||
static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
|
||||
//
|
||||
// Tuya identifiers, only to detect and warn users as they are incompatible.
|
||||
static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
|
||||
static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
|
||||
|
||||
struct Am43Packet {
|
||||
uint8_t length;
|
||||
uint8_t data[24];
|
||||
};
|
||||
|
||||
static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
|
||||
static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
|
||||
static const uint8_t CMD_GET_POSITION = 0xA7;
|
||||
static const uint8_t CMD_SEND_PIN = 0x17;
|
||||
static const uint8_t CMD_SET_STATE = 0x0A;
|
||||
static const uint8_t CMD_SET_POSITION = 0x0D;
|
||||
static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
|
||||
|
||||
static const uint8_t RESPONSE_ACK = 0x5A;
|
||||
static const uint8_t RESPONSE_NACK = 0xA5;
|
||||
|
||||
class Am43Encoder {
|
||||
public:
|
||||
Am43Packet *get_battery_level_request();
|
||||
Am43Packet *get_light_level_request();
|
||||
Am43Packet *get_position_request();
|
||||
Am43Packet *get_send_pin_request(uint16_t pin);
|
||||
Am43Packet *get_open_request();
|
||||
Am43Packet *get_close_request();
|
||||
Am43Packet *get_stop_request();
|
||||
Am43Packet *get_set_position_request(uint8_t position);
|
||||
|
||||
protected:
|
||||
void checksum_();
|
||||
Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
|
||||
Am43Packet packet_;
|
||||
};
|
||||
|
||||
class Am43Decoder {
|
||||
public:
|
||||
void decode(const uint8_t *data, uint16_t length);
|
||||
bool has_battery_level() { return this->has_battery_level_; }
|
||||
bool has_light_level() { return this->has_light_level_; }
|
||||
bool has_set_position_response() { return this->has_set_position_response_; }
|
||||
bool has_set_state_response() { return this->has_set_state_response_; }
|
||||
bool has_position() { return this->has_position_; }
|
||||
bool has_pin_response() { return this->has_pin_response_; }
|
||||
|
||||
union {
|
||||
uint8_t position_;
|
||||
uint8_t battery_level_;
|
||||
float light_level_;
|
||||
uint8_t set_position_ok_;
|
||||
uint8_t set_state_ok_;
|
||||
uint8_t pin_ok_;
|
||||
};
|
||||
|
||||
protected:
|
||||
bool has_battery_level_;
|
||||
bool has_light_level_;
|
||||
bool has_set_position_response_;
|
||||
bool has_set_state_response_;
|
||||
bool has_position_;
|
||||
bool has_pin_response_;
|
||||
};
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
36
esphome/components/am43/cover/__init__.py
Normal file
36
esphome/components/am43/cover/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import cover, ble_client
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
AUTO_LOAD = ["am43"]
|
||||
|
||||
CONF_INVERT_POSITION = "invert_position"
|
||||
|
||||
am43_ns = cg.esphome_ns.namespace("am43")
|
||||
Am43Component = am43_ns.class_(
|
||||
"Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cover.COVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Am43Component),
|
||||
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
|
||||
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
|
||||
yield cg.register_component(var, config)
|
||||
yield cover.register_cover(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
149
esphome/components/am43/cover/am43_cover.cpp
Normal file
149
esphome/components/am43/cover/am43_cover.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "am43_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
static const char *TAG = "am43_cover";
|
||||
|
||||
using namespace esphome::cover;
|
||||
|
||||
void Am43Component::dump_config() {
|
||||
LOG_COVER("", "AM43 Cover", this);
|
||||
ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
|
||||
}
|
||||
|
||||
void Am43Component::setup() {
|
||||
this->position = COVER_OPEN;
|
||||
this->encoder_ = new Am43Encoder();
|
||||
this->decoder_ = new Am43Decoder();
|
||||
this->logged_in_ = false;
|
||||
}
|
||||
|
||||
void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) {
|
||||
auto packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
|
||||
else
|
||||
this->logged_in_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
CoverTraits Am43Component::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void Am43Component::control(const CoverCall &call) {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
if (call.get_stop()) {
|
||||
auto packet = this->encoder_->get_stop_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
|
||||
if (this->invert_position_)
|
||||
pos = 1 - pos;
|
||||
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->logged_in_ = false;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
|
||||
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.handle != this->char_handle_)
|
||||
break;
|
||||
this->decoder_->decode(param->notify.value, param->notify.value_len);
|
||||
|
||||
if (this->decoder_->has_position()) {
|
||||
this->position = ((float) this->decoder_->position_ / 100.0);
|
||||
if (!this->invert_position_)
|
||||
this->position = 1 - this->position;
|
||||
if (this->position > 0.97)
|
||||
this->position = 1.0;
|
||||
if (this->position < 0.02)
|
||||
this->position = 0.0;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
if (this->decoder_->has_pin_response()) {
|
||||
if (this->decoder_->pin_ok_) {
|
||||
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
|
||||
auto packet = this->encoder_->get_position_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
|
||||
ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
|
||||
|
||||
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
|
||||
ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
45
esphome/components/am43/cover/am43_cover.h
Normal file
45
esphome/components/am43/cover/am43_cover.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/cover/cover.h"
|
||||
#include "esphome/components/am43/am43_base.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
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 dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
cover::CoverTraits get_traits() override;
|
||||
void set_pin(uint16_t pin) { this->pin_ = pin; }
|
||||
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
|
||||
|
||||
protected:
|
||||
void control(const cover::CoverCall &call) override;
|
||||
uint16_t char_handle_;
|
||||
uint16_t pin_;
|
||||
bool invert_position_;
|
||||
Am43Encoder *encoder_;
|
||||
Am43Decoder *decoder_;
|
||||
bool logged_in_;
|
||||
|
||||
float position_;
|
||||
};
|
||||
|
||||
} // namespace am43
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
46
esphome/components/am43/sensor.py
Normal file
46
esphome/components/am43/sensor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_BATTERY_LEVEL,
|
||||
ICON_BATTERY,
|
||||
CONF_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
|
||||
am43_ns = cg.esphome_ns.namespace("am43")
|
||||
Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Am43),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_BATTERY, 0
|
||||
),
|
||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("120s"))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery(sens))
|
||||
|
||||
if CONF_ILLUMINANCE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
|
||||
cg.add(var.set_illuminance(sens))
|
@@ -90,19 +90,24 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
if (this->codec_->has_running()) {
|
||||
this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF;
|
||||
}
|
||||
if (this->codec_->has_unit()) {
|
||||
this->fahrenheit_ = (this->codec_->unit_ == 'f');
|
||||
ESP_LOGD(TAG, "Anova units is %s", this->fahrenheit_ ? "fahrenheit" : "celcius");
|
||||
this->current_request_++;
|
||||
}
|
||||
this->publish_state();
|
||||
|
||||
if (this->current_request_ > 0) {
|
||||
if (this->current_request_ > 1) {
|
||||
AnovaPacket *pkt = nullptr;
|
||||
switch (this->current_request_++) {
|
||||
case 1:
|
||||
case 2:
|
||||
pkt = this->codec_->get_read_target_temp_request();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
pkt = this->codec_->get_read_current_temp_request();
|
||||
break;
|
||||
default:
|
||||
this->current_request_ = 0;
|
||||
this->current_request_ = 1;
|
||||
break;
|
||||
}
|
||||
if (pkt != nullptr) {
|
||||
@@ -121,12 +126,16 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
}
|
||||
}
|
||||
|
||||
void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
|
||||
|
||||
void Anova::update() {
|
||||
if (this->node_state != espbt::ClientState::Established)
|
||||
return;
|
||||
|
||||
if (this->current_request_ == 0) {
|
||||
if (this->current_request_ < 2) {
|
||||
auto pkt = this->codec_->get_read_device_status_request();
|
||||
if (this->current_request_ == 0)
|
||||
auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
|
@@ -36,12 +36,14 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
||||
traits.set_visual_temperature_step(0.1);
|
||||
return traits;
|
||||
}
|
||||
void set_unit_of_measurement(const char *);
|
||||
|
||||
protected:
|
||||
AnovaCodec *codec_;
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
uint16_t char_handle_;
|
||||
uint8_t current_request_;
|
||||
bool fahrenheit_;
|
||||
};
|
||||
|
||||
} // namespace anova
|
||||
|
@@ -3,6 +3,10 @@
|
||||
namespace esphome {
|
||||
namespace anova {
|
||||
|
||||
float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
|
||||
|
||||
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
|
||||
|
||||
AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
this->packet_.length = strlen((char *) this->packet_.data);
|
||||
this->packet_.data[this->packet_.length] = '\0';
|
||||
@@ -42,6 +46,8 @@ AnovaPacket *AnovaCodec::get_read_data_request() {
|
||||
|
||||
AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
|
||||
this->current_query_ = SET_TARGET_TEMPERATURE;
|
||||
if (this->fahrenheit_)
|
||||
temperature = ctof(temperature);
|
||||
sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
|
||||
return this->clean_packet_();
|
||||
}
|
||||
@@ -67,7 +73,6 @@ AnovaPacket *AnovaCodec::get_stop_request() {
|
||||
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
|
||||
memset(this->buf_, 0, 32);
|
||||
strncpy(this->buf_, (char *) data, length);
|
||||
ESP_LOGV("anova", "Received: %s\n", this->buf_);
|
||||
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
|
||||
switch (this->current_query_) {
|
||||
case READ_DEVICE_STATUS: {
|
||||
@@ -97,19 +102,32 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
|
||||
}
|
||||
case READ_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = strtof(this->buf_, nullptr);
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case SET_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = strtof(this->buf_, nullptr);
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case READ_CURRENT_TEMPERATURE: {
|
||||
this->current_temp_ = strtof(this->buf_, nullptr);
|
||||
if (this->fahrenheit_)
|
||||
this->current_temp_ = ftoc(this->current_temp_);
|
||||
this->has_current_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case SET_UNIT:
|
||||
case READ_UNIT: {
|
||||
this->unit_ = this->buf_[0];
|
||||
this->fahrenheit_ = this->buf_[0] == 'f';
|
||||
this->has_unit_ = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@@ -71,6 +71,7 @@ class AnovaCodec {
|
||||
bool has_unit_;
|
||||
bool has_running_;
|
||||
char buf_[32];
|
||||
bool fahrenheit_;
|
||||
|
||||
CurrentQuery current_query_;
|
||||
};
|
||||
|
@@ -1,7 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate, ble_client
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
|
||||
|
||||
UNITS = {
|
||||
"f": "f",
|
||||
"c": "c",
|
||||
}
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
@@ -12,14 +17,20 @@ Anova = anova_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
climate.CLIMATE_SCHEMA.extend({cv.GenerateID(): cv.declare_id(Anova)})
|
||||
climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Anova),
|
||||
cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield climate.register_climate(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
|
||||
|
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
ICON_LIGHTBULB,
|
||||
@@ -21,7 +20,10 @@ TYPES = {
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_LIGHTBULB,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import base64
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
@@ -6,6 +8,7 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_ID,
|
||||
CONF_KEY,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
@@ -19,7 +22,7 @@ from esphome.const import (
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["async_tcp"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
@@ -41,6 +44,22 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
"float[]": cg.std_vector.template(float),
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
|
||||
|
||||
def validate_encryption_key(value):
|
||||
value = cv.string_strict(value)
|
||||
try:
|
||||
decoded = base64.b64decode(value, validate=True)
|
||||
except ValueError as err:
|
||||
raise cv.Invalid("Invalid key format, please check it's using base64") from err
|
||||
|
||||
if len(decoded) != 32:
|
||||
raise cv.Invalid("Encryption key must be base64 and 32 bytes long")
|
||||
|
||||
# Return original data for roundtrip conversion
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -63,6 +82,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ENCRYPTION): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): validate_encryption_key,
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -92,6 +116,15 @@ async def to_code(config):
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
if CONF_ENCRYPTION in config:
|
||||
conf = config[CONF_ENCRYPTION]
|
||||
decoded = base64.b64decode(conf[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.1")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
cg.add_define("USE_API")
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
@@ -39,6 +39,7 @@ service APIConnection {
|
||||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +214,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
bool disabled_by_default = 7;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
@@ -242,6 +244,7 @@ message ListEntitiesCoverResponse {
|
||||
bool supports_position = 6;
|
||||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
bool disabled_by_default = 9;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@@ -309,6 +312,7 @@ message ListEntitiesFanResponse {
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
bool disabled_by_default = 9;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@@ -352,6 +356,18 @@ message FanCommandRequest {
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
enum ColorMode {
|
||||
COLOR_MODE_UNKNOWN = 0;
|
||||
COLOR_MODE_ON_OFF = 1;
|
||||
COLOR_MODE_BRIGHTNESS = 2;
|
||||
COLOR_MODE_WHITE = 7;
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11;
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19;
|
||||
COLOR_MODE_RGB = 35;
|
||||
COLOR_MODE_RGB_WHITE = 39;
|
||||
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47;
|
||||
COLOR_MODE_RGB_COLD_WARM_WHITE = 51;
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -362,13 +378,16 @@ message ListEntitiesLightResponse {
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
repeated ColorMode supported_color_modes = 12;
|
||||
// next four supports_* are for legacy clients, newer clients should use color modes
|
||||
bool legacy_supports_brightness = 5 [deprecated=true];
|
||||
bool legacy_supports_rgb = 6 [deprecated=true];
|
||||
bool legacy_supports_white_value = 7 [deprecated=true];
|
||||
bool legacy_supports_color_temperature = 8 [deprecated=true];
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
bool disabled_by_default = 13;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
@@ -379,12 +398,15 @@ message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
ColorMode color_mode = 11;
|
||||
float color_brightness = 10;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
float cold_white = 12;
|
||||
float warm_white = 13;
|
||||
string effect = 9;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
@@ -398,6 +420,8 @@ message LightCommandRequest {
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_color_mode = 22;
|
||||
ColorMode color_mode = 23;
|
||||
bool has_color_brightness = 20;
|
||||
float color_brightness = 21;
|
||||
bool has_rgb = 6;
|
||||
@@ -408,6 +432,10 @@ message LightCommandRequest {
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_cold_white = 24;
|
||||
float cold_white = 25;
|
||||
bool has_warm_white = 26;
|
||||
float warm_white = 27;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
@@ -420,6 +448,7 @@ message LightCommandRequest {
|
||||
enum SensorStateClass {
|
||||
STATE_CLASS_NONE = 0;
|
||||
STATE_CLASS_MEASUREMENT = 1;
|
||||
STATE_CLASS_TOTAL_INCREASING = 2;
|
||||
}
|
||||
|
||||
enum SensorLastResetType {
|
||||
@@ -444,7 +473,9 @@ message ListEntitiesSensorResponse {
|
||||
bool force_update = 8;
|
||||
string device_class = 9;
|
||||
SensorStateClass state_class = 10;
|
||||
SensorLastResetType last_reset_type = 11;
|
||||
// Last reset type removed in 2021.9.0
|
||||
SensorLastResetType legacy_last_reset_type = 11;
|
||||
bool disabled_by_default = 12;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
@@ -472,6 +503,7 @@ message ListEntitiesSwitchResponse {
|
||||
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
bool disabled_by_default = 7;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@@ -504,6 +536,7 @@ message ListEntitiesTextSensorResponse {
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
@@ -524,9 +557,10 @@ enum LogLevel {
|
||||
LOG_LEVEL_ERROR = 1;
|
||||
LOG_LEVEL_WARN = 2;
|
||||
LOG_LEVEL_INFO = 3;
|
||||
LOG_LEVEL_DEBUG = 4;
|
||||
LOG_LEVEL_VERBOSE = 5;
|
||||
LOG_LEVEL_VERY_VERBOSE = 6;
|
||||
LOG_LEVEL_CONFIG = 4;
|
||||
LOG_LEVEL_DEBUG = 5;
|
||||
LOG_LEVEL_VERBOSE = 6;
|
||||
LOG_LEVEL_VERY_VERBOSE = 7;
|
||||
}
|
||||
message SubscribeLogsRequest {
|
||||
option (id) = 28;
|
||||
@@ -541,7 +575,6 @@ message SubscribeLogsResponse {
|
||||
option (no_delay) = false;
|
||||
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
@@ -663,6 +696,7 @@ message ListEntitiesCameraResponse {
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
bool disabled_by_default = 5;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
@@ -755,6 +789,7 @@ message ListEntitiesClimateResponse {
|
||||
repeated string supported_custom_fan_modes = 15;
|
||||
repeated ClimatePreset supported_presets = 16;
|
||||
repeated string supported_custom_presets = 17;
|
||||
bool disabled_by_default = 18;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -822,6 +857,7 @@ message ListEntitiesNumberResponse {
|
||||
float min_value = 6;
|
||||
float max_value = 7;
|
||||
float step = 8;
|
||||
bool disabled_by_default = 9;
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
@@ -844,3 +880,40 @@ message NumberCommandRequest {
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
|
||||
// ==================== SELECT ====================
|
||||
message ListEntitiesSelectResponse {
|
||||
option (id) = 52;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
repeated string options = 6;
|
||||
bool disabled_by_default = 7;
|
||||
}
|
||||
message SelectStateResponse {
|
||||
option (id) = 53;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
// If the select does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
message SelectCommandRequest {
|
||||
option (id) = 54;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include <cerrno>
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
@@ -18,145 +19,146 @@ namespace api {
|
||||
|
||||
static const char *const TAG = "api.connection";
|
||||
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
#if defined(USE_API_PLAINTEXT)
|
||||
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
#elif defined(USE_API_NOISE)
|
||||
helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
#else
|
||||
#error "No frame helper defined"
|
||||
#endif
|
||||
}
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
|
||||
void APIConnection::on_disconnect_() { this->remove_ = true; }
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
|
||||
APIError err = helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
return;
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t consumed;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_size_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_size = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_type_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_type = msg_type_varint->as_uint32();
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
client_info_ = helper_->getpeername();
|
||||
helper_->set_log_info(client_info_);
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (this->next_close_) {
|
||||
this->disconnect_client();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
if (this->next_close_) {
|
||||
// requested a disconnect
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
APIError err = helper_->loop();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
return;
|
||||
}
|
||||
ReadPacketBuffer buffer;
|
||||
err = helper_->read_packet(&buffer);
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = millis();
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
if (this->remove_)
|
||||
return;
|
||||
}
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
const uint32_t now = millis();
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
} else if (now - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request(PingRequest());
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
|
||||
uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state_subs_at_ != -1) {
|
||||
const auto &subs = this->parent_->get_state_subs();
|
||||
if (state_subs_at_ >= subs.size()) {
|
||||
state_subs_at_ = -1;
|
||||
} else {
|
||||
auto &it = subs[state_subs_at_];
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
resp.attribute = it.attribute.value();
|
||||
if (this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
state_subs_at_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
||||
// pass
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
@@ -176,6 +178,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
||||
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@@ -207,6 +210,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
@@ -268,6 +272,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
msg.disabled_by_default = fan->is_disabled_by_default();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
@@ -286,6 +291,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
// Prefer level
|
||||
call.set_speed(msg.speed_level);
|
||||
} else if (msg.has_speed) {
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
|
||||
}
|
||||
if (msg.has_direction)
|
||||
@@ -301,22 +307,21 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
auto color_mode = values.get_color_mode();
|
||||
LightStateResponse resp{};
|
||||
|
||||
resp.key = light->get_object_id_hash();
|
||||
resp.state = values.is_on();
|
||||
if (traits.get_supports_brightness())
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
resp.color_brightness = values.get_color_brightness();
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
resp.white = values.get_white();
|
||||
if (traits.get_supports_color_temperature())
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
|
||||
resp.brightness = values.get_brightness();
|
||||
resp.color_brightness = values.get_color_brightness();
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
resp.white = values.get_white();
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
@@ -328,11 +333,21 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
msg.supports_brightness = traits.get_supports_brightness();
|
||||
msg.supports_rgb = traits.get_supports_rgb();
|
||||
msg.supports_white_value = traits.get_supports_rgb_white_value();
|
||||
msg.supports_color_temperature = traits.get_supports_color_temperature();
|
||||
if (msg.supports_color_temperature) {
|
||||
|
||||
msg.disabled_by_default = light->is_disabled_by_default();
|
||||
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
|
||||
msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
|
||||
msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
|
||||
msg.legacy_supports_white_value =
|
||||
msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
|
||||
msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
|
||||
|
||||
if (msg.legacy_supports_color_temperature) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
@@ -353,6 +368,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_brightness)
|
||||
call.set_brightness(msg.brightness);
|
||||
if (msg.has_color_mode)
|
||||
call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
|
||||
if (msg.has_color_brightness)
|
||||
call.set_color_brightness(msg.color_brightness);
|
||||
if (msg.has_rgb) {
|
||||
@@ -364,6 +381,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
call.set_white(msg.white);
|
||||
if (msg.has_color_temperature)
|
||||
call.set_color_temperature(msg.color_temperature);
|
||||
if (msg.has_cold_white)
|
||||
call.set_cold_white(msg.cold_white);
|
||||
if (msg.has_warm_white)
|
||||
call.set_warm_white(msg.warm_white);
|
||||
if (msg.has_transition_length)
|
||||
call.set_transition_length(msg.transition_length);
|
||||
if (msg.has_flash_length)
|
||||
@@ -399,7 +420,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
||||
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
|
||||
msg.disabled_by_default = sensor->is_disabled_by_default();
|
||||
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
@@ -423,6 +444,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
msg.disabled_by_default = a_switch->is_disabled_by_default();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
@@ -457,6 +479,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
msg.icon = text_sensor->get_icon();
|
||||
msg.disabled_by_default = text_sensor->is_disabled_by_default();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@@ -500,6 +523,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
msg.object_id = climate->get_object_id();
|
||||
msg.name = climate->get_name();
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
|
||||
msg.disabled_by_default = climate->is_disabled_by_default();
|
||||
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
|
||||
@@ -572,6 +598,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.name = number->get_name();
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->traits.get_icon();
|
||||
msg.disabled_by_default = number->is_disabled_by_default();
|
||||
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
@@ -590,6 +617,42 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool APIConnection::send_select_state(select::Select *select, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SelectStateResponse resp{};
|
||||
resp.key = select->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !select->has_state();
|
||||
return this->send_select_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_select_info(select::Select *select) {
|
||||
ListEntitiesSelectResponse msg;
|
||||
msg.key = select->get_object_id_hash();
|
||||
msg.object_id = select->get_object_id();
|
||||
msg.name = select->get_name();
|
||||
msg.unique_id = get_default_unique_id("select", select);
|
||||
msg.icon = select->traits.get_icon();
|
||||
msg.disabled_by_default = select->is_disabled_by_default();
|
||||
|
||||
for (const auto &option : select->traits.get_options())
|
||||
msg.options.push_back(option);
|
||||
|
||||
return this->send_list_entities_select_response(msg);
|
||||
}
|
||||
void APIConnection::select_command(const SelectCommandRequest &msg) {
|
||||
select::Select *select = App.get_select_by_key(msg.key);
|
||||
if (select == nullptr)
|
||||
return;
|
||||
|
||||
auto call = select->make_call();
|
||||
call.set_option(msg.state);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
@@ -604,6 +667,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
msg.object_id = camera->get_object_id();
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", camera);
|
||||
msg.disabled_by_default = camera->is_disabled_by_default();
|
||||
return this->send_list_entities_camera_response(msg);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
@@ -632,8 +696,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
auto buffer = this->create_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// SubscribeLogsResponse - 29
|
||||
@@ -649,13 +711,13 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
|
||||
this->helper_->set_log_info(client_info_);
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 5;
|
||||
resp.api_version_minor = 6;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
@@ -667,7 +729,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
@@ -685,9 +747,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
#ifdef ARDUINO_BOARD
|
||||
resp.model = ARDUINO_BOARD;
|
||||
#endif
|
||||
resp.model = ESPHOME_BOARD;
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
#endif
|
||||
@@ -715,57 +775,39 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
}
|
||||
}
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
resp.attribute = it.attribute.value();
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
state_subs_at_ = 0;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (this->remove_)
|
||||
return false;
|
||||
if (!this->helper_->can_write_without_blocking())
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
|
||||
ProtoVarInt(message_type).encode(header);
|
||||
|
||||
size_t needed_space = buffer.get_buffer()->size() + header.size();
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(0);
|
||||
if (needed_space > this->client_->space()) {
|
||||
// SubscribeLogsResponse
|
||||
if (message_type != 29) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return false;
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
|
||||
ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE);
|
||||
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size(),
|
||||
ASYNC_WRITE_FLAG_COPY);
|
||||
bool ret = this->client_->send();
|
||||
return ret;
|
||||
this->last_traffic_ = millis();
|
||||
return true;
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||
this->client_->close();
|
||||
this->helper_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
|
@@ -5,16 +5,17 @@
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
#include "api_frame_helper.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
virtual ~APIConnection() = default;
|
||||
|
||||
void disconnect_client();
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
@@ -67,6 +68,11 @@ class APIConnection : public APIServerConnection {
|
||||
bool send_number_state(number::Number *number, float state);
|
||||
bool send_number_info(number::Number *number);
|
||||
void number_command(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state(select::Select *select, std::string state);
|
||||
bool send_select_info(select::Select *select);
|
||||
void select_command(const SelectCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
@@ -81,10 +87,7 @@ class APIConnection : public APIServerConnection {
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||
// we initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
}
|
||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
@@ -95,12 +98,7 @@ class APIConnection : public APIServerConnection {
|
||||
#endif
|
||||
HelloResponse hello(const HelloRequest &msg) override;
|
||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||
// remote initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override;
|
||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
@@ -130,19 +128,16 @@ class APIConnection : public APIServerConnection {
|
||||
void on_unauthenticated_access() override;
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer() override {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
// FIXME: ensure no recursive writes can happen
|
||||
this->proto_write_buffer_.clear();
|
||||
return {&this->proto_write_buffer_};
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void parse_recv_buffer_();
|
||||
bool send_(const void *buf, size_t len, bool force);
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
@@ -152,8 +147,10 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
// Buffer used to encode proto messages
|
||||
// Re-use to prevent allocations
|
||||
std::vector<uint8_t> proto_write_buffer_;
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
@@ -165,12 +162,11 @@ class APIConnection : public APIServerConnection {
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool current_nodelay_{false};
|
||||
bool next_close_{false};
|
||||
AsyncClient *client_;
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
int state_subs_at_ = -1;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
938
esphome/components/api/api_frame_helper.cpp
Normal file
938
esphome/components/api/api_frame_helper.cpp
Normal file
@@ -0,0 +1,938 @@
|
||||
#include "api_frame_helper.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *const TAG = "api.socket";
|
||||
|
||||
/// Is the given return value (from read/write syscalls) a wouldblock error?
|
||||
bool is_would_block(ssize_t ret) {
|
||||
if (ret == -1) {
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
}
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
const char *api_error_to_str(APIError err) {
|
||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||
if (err == APIError::OK) {
|
||||
return "OK";
|
||||
} else if (err == APIError::WOULD_BLOCK) {
|
||||
return "WOULD_BLOCK";
|
||||
} else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
return "BAD_HANDSHAKE_PACKET_LEN";
|
||||
} else if (err == APIError::BAD_INDICATOR) {
|
||||
return "BAD_INDICATOR";
|
||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||
return "BAD_DATA_PACKET";
|
||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||
return "TCP_NODELAY_FAILED";
|
||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||
return "TCP_NONBLOCKING_FAILED";
|
||||
} else if (err == APIError::CLOSE_FAILED) {
|
||||
return "CLOSE_FAILED";
|
||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||
return "SHUTDOWN_FAILED";
|
||||
} else if (err == APIError::BAD_STATE) {
|
||||
return "BAD_STATE";
|
||||
} else if (err == APIError::BAD_ARG) {
|
||||
return "BAD_ARG";
|
||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||
return "SOCKET_READ_FAILED";
|
||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||
return "SOCKET_WRITE_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||
return "HANDSHAKESTATE_READ_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||
return "HANDSHAKESTATE_WRITE_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||
return "HANDSHAKESTATE_BAD_STATE";
|
||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||
return "CIPHERSTATE_DECRYPT_FAILED";
|
||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||
return "CIPHERSTATE_ENCRYPT_FAILED";
|
||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||
return "OUT_OF_MEMORY";
|
||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||
return "HANDSHAKESTATE_SETUP_FAILED";
|
||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
|
||||
/// Convert a noise error code to a readable error
|
||||
std::string noise_err_to_str(int err) {
|
||||
if (err == NOISE_ERROR_NO_MEMORY)
|
||||
return "NO_MEMORY";
|
||||
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||
return "UNKNOWN_ID";
|
||||
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||
return "UNKNOWN_NAME";
|
||||
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||
return "MAC_FAILURE";
|
||||
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||
return "NOT_APPLICABLE";
|
||||
if (err == NOISE_ERROR_SYSTEM)
|
||||
return "SYSTEM";
|
||||
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||
return "REMOTE_KEY_REQUIRED";
|
||||
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||
return "LOCAL_KEY_REQUIRED";
|
||||
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||
return "PSK_REQUIRED";
|
||||
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||
return "INVALID_LENGTH";
|
||||
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||
return "INVALID_PARAM";
|
||||
if (err == NOISE_ERROR_INVALID_STATE)
|
||||
return "INVALID_STATE";
|
||||
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||
return "INVALID_NONCE";
|
||||
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||
return "INVALID_PRIVATE_KEY";
|
||||
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||
return "INVALID_PUBLIC_KEY";
|
||||
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||
return "INVALID_FORMAT";
|
||||
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||
return "INVALID_SIGNATURE";
|
||||
return to_string(err);
|
||||
}
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APINoiseFrameHelper::init() {
|
||||
if (state_ != State::INITIALIZE || socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
// init prologue
|
||||
prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT));
|
||||
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
}
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
APIError err = state_action_();
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK;
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
if (!tx_buf_.empty()) {
|
||||
err = try_send_tx_buf_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
*
|
||||
* @param frame: The struct to hold the frame information in.
|
||||
* msg_start: points to the start of the payload - this pointer is only valid until the next
|
||||
* try_receive_raw_ call
|
||||
*
|
||||
* @return 0 if a full packet is in rx_buf_
|
||||
* @return -1 if error, check errno.
|
||||
*
|
||||
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
||||
* errno ENOMEM: Not enough memory for reading packet.
|
||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||
*/
|
||||
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
}
|
||||
|
||||
// read header
|
||||
if (rx_header_buf_len_ < 3) {
|
||||
// no header information yet
|
||||
size_t to_read = 3 - rx_header_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
}
|
||||
rx_header_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
// not a full read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
// header reading done
|
||||
}
|
||||
|
||||
// read body
|
||||
uint8_t indicator = rx_header_buf_[0];
|
||||
if (indicator != 0x01) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", indicator);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
|
||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||
|
||||
if (state_ != State::DATA && msg_size > 128) {
|
||||
// for handshake message only permit up to 128 bytes
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
|
||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||
}
|
||||
|
||||
// reserve space for body
|
||||
if (rx_buf_.size() != msg_size) {
|
||||
rx_buf_.resize(msg_size);
|
||||
}
|
||||
|
||||
if (rx_buf_len_ < msg_size) {
|
||||
// more data to read
|
||||
size_t to_read = msg_size - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
}
|
||||
rx_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
// not all read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
rx_header_buf_len_ = 0;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/** To be called from read/write methods.
|
||||
*
|
||||
* This method runs through the internal handshake methods, if in that state.
|
||||
*
|
||||
* If the handshake is still active when this method returns and a read/write can't take place at
|
||||
* the moment, returns WOULD_BLOCK.
|
||||
* If an error occured, returns that error. Only returns OK if the transport is ready for data
|
||||
* traffic.
|
||||
*/
|
||||
APIError APINoiseFrameHelper::state_action_() {
|
||||
int err;
|
||||
APIError aerr;
|
||||
if (state_ == State::INITIALIZE) {
|
||||
HELPER_LOG("Bad state for method: %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
if (state_ == State::CLIENT_HELLO) {
|
||||
// waiting for client hello
|
||||
ParsedFrame frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// ignore contents, may be used in future for flags
|
||||
prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
|
||||
prologue_.push_back((uint8_t) frame.msg.size());
|
||||
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
|
||||
|
||||
state_ = State::SERVER_HELLO;
|
||||
}
|
||||
if (state_ == State::SERVER_HELLO) {
|
||||
// send server hello
|
||||
uint8_t msg[1];
|
||||
msg[0] = 0x01; // chosen proto
|
||||
aerr = write_frame_(msg, 1);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
// start handshake
|
||||
aerr = init_handshake_();
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
state_ = State::HANDSHAKE;
|
||||
}
|
||||
if (state_ == State::HANDSHAKE) {
|
||||
int action = noise_handshakestate_get_action(handshake_);
|
||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||
// waiting for handshake msg
|
||||
ParsedFrame frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
if (frame.msg.empty()) {
|
||||
send_explicit_handshake_reject_("Empty handshake message");
|
||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||
} else if (frame.msg[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
|
||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||
}
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
|
||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
// TODO: explicit rejection
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
|
||||
if (err == NOISE_ERROR_MAC_FAILURE) {
|
||||
send_explicit_handshake_reject_("Handshake MAC failure");
|
||||
} else {
|
||||
send_explicit_handshake_reject_("Handshake error");
|
||||
}
|
||||
return APIError::HANDSHAKESTATE_READ_FAILED;
|
||||
}
|
||||
|
||||
aerr = check_handshake_finished_();
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
} else if (action == NOISE_ACTION_WRITE_MESSAGE) {
|
||||
uint8_t buffer[65];
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||
|
||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_WRITE_FAILED;
|
||||
}
|
||||
buffer[0] = 0x00; // success
|
||||
|
||||
aerr = write_frame_(buffer, mbuf.size + 1);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
aerr = check_handshake_finished_();
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
} else {
|
||||
// bad state for action
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad action for handshake: %d", action);
|
||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||
}
|
||||
}
|
||||
if (state_ == State::CLOSED || state_ == State::FAILED) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(reason.size() + 1);
|
||||
data[0] = 0x01; // failure
|
||||
for (size_t i = 0; i < reason.size(); i++) {
|
||||
data[i + 1] = (uint8_t) reason[i];
|
||||
}
|
||||
write_frame_(data.data(), data.size());
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
ParsedFrame frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size());
|
||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_DECRYPT_FAILED;
|
||||
}
|
||||
|
||||
size_t msg_size = mbuf.size;
|
||||
uint8_t *msg_data = frame.msg.data();
|
||||
if (msg_size < 4) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
// uint16_t type;
|
||||
// uint16_t data_len;
|
||||
// uint8_t *data;
|
||||
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
|
||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||
if (data_len > msg_size - 4) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->container = std::move(frame.msg);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
size_t padding = 0;
|
||||
size_t msg_len = 4 + payload_len + padding;
|
||||
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
|
||||
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
|
||||
if (tmpbuf == nullptr) {
|
||||
HELPER_LOG("Could not allocate for writing packet");
|
||||
return APIError::OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
tmpbuf[0] = 0x01; // indicator
|
||||
// tmpbuf[1], tmpbuf[2] to be set later
|
||||
const uint8_t msg_offset = 3;
|
||||
const uint8_t payload_offset = msg_offset + 4;
|
||||
tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type
|
||||
tmpbuf[msg_offset + 1] = (uint8_t) type;
|
||||
tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len
|
||||
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
|
||||
// copy data
|
||||
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
||||
// fill padding with zeros
|
||||
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
|
||||
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
|
||||
}
|
||||
|
||||
size_t total_len = 3 + mbuf.size;
|
||||
tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
|
||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
||||
// write raw to not have two packets sent if NAGLE disabled
|
||||
aerr = write_raw_(&tmpbuf[0], total_len);
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
while (state_ != State::CLOSED && !tx_buf_.empty()) {
|
||||
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
|
||||
if (sent == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||||
break;
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent == 0) {
|
||||
break;
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
|
||||
}
|
||||
|
||||
return APIError::OK;
|
||||
}
|
||||
/** Write the data to the socket, or buffer it a write would block
|
||||
*
|
||||
* @param data The data to write
|
||||
* @param len The length of data
|
||||
*/
|
||||
APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
return APIError::OK;
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// try to empty tx_buf_ first
|
||||
aerr = try_send_tx_buf_();
|
||||
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
|
||||
return aerr;
|
||||
}
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
return APIError::OK;
|
||||
} else if (sent == -1) {
|
||||
// an error occured
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent != len) {
|
||||
// partially sent, add end to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
|
||||
return APIError::OK;
|
||||
}
|
||||
// fully sent
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
APIError aerr;
|
||||
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
header[1] = (uint8_t)(len >> 8);
|
||||
header[2] = (uint8_t) len;
|
||||
|
||||
aerr = write_raw_(header, 3);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
aerr = write_raw_(data, len);
|
||||
return aerr;
|
||||
}
|
||||
|
||||
/** Initiate the data structures for the handshake.
|
||||
*
|
||||
* @return 0 on success, -1 on error (check errno)
|
||||
*/
|
||||
APIError APINoiseFrameHelper::init_handshake_() {
|
||||
int err;
|
||||
memset(&nid_, 0, sizeof(nid_));
|
||||
// const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
|
||||
// err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
|
||||
nid_.pattern_id = NOISE_PATTERN_NN;
|
||||
nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
|
||||
nid_.dh_id = NOISE_DH_CURVE25519;
|
||||
nid_.prefix_id = NOISE_PREFIX_STANDARD;
|
||||
nid_.hybrid_id = NOISE_DH_NONE;
|
||||
nid_.hash_id = NOISE_HASH_SHA256;
|
||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||
|
||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
|
||||
const auto &psk = ctx_->get_psk();
|
||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
|
||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||
prologue_ = {};
|
||||
|
||||
err = noise_handshakestate_start(handshake_);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||
assert(state_ == State::HANDSHAKE);
|
||||
|
||||
int action = noise_handshakestate_get_action(handshake_);
|
||||
if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
|
||||
return APIError::OK;
|
||||
if (action != NOISE_ACTION_SPLIT) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad action for handshake: %d", action);
|
||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||
}
|
||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
||||
}
|
||||
|
||||
HELPER_LOG("Handshake complete!");
|
||||
noise_handshakestate_free(handshake_);
|
||||
handshake_ = nullptr;
|
||||
state_ = State::DATA;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APINoiseFrameHelper::~APINoiseFrameHelper() {
|
||||
if (handshake_ != nullptr) {
|
||||
noise_handshakestate_free(handshake_);
|
||||
handshake_ = nullptr;
|
||||
}
|
||||
if (send_cipher_ != nullptr) {
|
||||
noise_cipherstate_free(send_cipher_);
|
||||
send_cipher_ = nullptr;
|
||||
}
|
||||
if (recv_cipher_ != nullptr) {
|
||||
noise_cipherstate_free(recv_cipher_);
|
||||
recv_cipher_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = socket_->close();
|
||||
if (err == -1)
|
||||
return APIError::CLOSE_FAILED;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::shutdown(int how) {
|
||||
int err = socket_->shutdown(how);
|
||||
if (err == -1)
|
||||
return APIError::SHUTDOWN_FAILED;
|
||||
if (how == SHUT_RDWR) {
|
||||
state_ = State::CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
extern "C" {
|
||||
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
||||
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); }
|
||||
}
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APIPlaintextFrameHelper::init() {
|
||||
if (state_ != State::INITIALIZE || socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
int enable = 1;
|
||||
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
state_ = State::DATA;
|
||||
return APIError::OK;
|
||||
}
|
||||
/// Not used for plaintext
|
||||
APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
// try send pending TX data
|
||||
if (!tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
*
|
||||
* @param frame: The struct to hold the frame information in.
|
||||
* msg: store the parsed frame in that struct
|
||||
*
|
||||
* @return See APIError
|
||||
*
|
||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
*/
|
||||
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
}
|
||||
|
||||
// read header
|
||||
while (!rx_header_parsed_) {
|
||||
uint8_t data;
|
||||
ssize_t received = socket_->read(&data, 1);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
}
|
||||
rx_header_buf_.push_back(data);
|
||||
|
||||
// try parse header
|
||||
if (rx_header_buf_[0] != 0x00) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
|
||||
size_t i = 1;
|
||||
uint32_t consumed = 0;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
||||
if (!msg_size_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
continue;
|
||||
}
|
||||
|
||||
i += consumed;
|
||||
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
|
||||
if (!msg_type_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
continue;
|
||||
}
|
||||
rx_header_parsed_type_ = msg_type_varint->as_uint32();
|
||||
rx_header_parsed_ = true;
|
||||
}
|
||||
// header reading done
|
||||
|
||||
// reserve space for body
|
||||
if (rx_buf_.size() != rx_header_parsed_len_) {
|
||||
rx_buf_.resize(rx_header_parsed_len_);
|
||||
}
|
||||
|
||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||
// more data to read
|
||||
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
}
|
||||
rx_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
// not all read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
rx_header_buf_.clear();
|
||||
rx_header_parsed_ = false;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
ParsedFrame frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
buffer->container = std::move(frame.msg);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data_len = rx_header_parsed_len_;
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(payload_len).encode(header);
|
||||
ProtoVarInt(type).encode(header);
|
||||
|
||||
aerr = write_raw_(&header[0], header.size());
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
aerr = write_raw_(payload, payload_len);
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
||||
// try send from tx_buf
|
||||
while (state_ != State::CLOSED && !tx_buf_.empty()) {
|
||||
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
|
||||
if (is_would_block(sent)) {
|
||||
break;
|
||||
} else if (sent == -1) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
// TODO: inefficient if multiple packets in txbuf
|
||||
// replace with deque of buffers
|
||||
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
|
||||
}
|
||||
|
||||
return APIError::OK;
|
||||
}
|
||||
/** Write the data to the socket, or buffer it a write would block
|
||||
*
|
||||
* @param data The data to write
|
||||
* @param len The length of data
|
||||
*/
|
||||
APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
return APIError::OK;
|
||||
int err;
|
||||
APIError aerr;
|
||||
|
||||
// uncomment for even more debugging
|
||||
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// try to empty tx_buf_ first
|
||||
aerr = try_send_tx_buf_();
|
||||
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
|
||||
return aerr;
|
||||
}
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data, data + len);
|
||||
return APIError::OK;
|
||||
} else if (sent == -1) {
|
||||
// an error occured
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if (sent != len) {
|
||||
// partially sent, add end to tx_buf
|
||||
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
|
||||
return APIError::OK;
|
||||
}
|
||||
// fully sent
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = socket_->close();
|
||||
if (err == -1)
|
||||
return APIError::CLOSE_FAILED;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::shutdown(int how) {
|
||||
int err = socket_->shutdown(how);
|
||||
if (err == -1)
|
||||
return APIError::SHUTDOWN_FAILED;
|
||||
if (how == SHUT_RDWR) {
|
||||
state_ = State::CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
180
esphome/components/api/api_frame_helper.h
Normal file
180
esphome/components/api/api_frame_helper.h
Normal file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
#include "noise/protocol.h"
|
||||
#endif
|
||||
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "api_noise_context.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
size_t data_offset;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
struct PacketBuffer {
|
||||
const std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint8_t data_offset;
|
||||
uint8_t data_len;
|
||||
};
|
||||
|
||||
enum class APIError : int {
|
||||
OK = 0,
|
||||
WOULD_BLOCK = 1001,
|
||||
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
||||
BAD_INDICATOR = 1003,
|
||||
BAD_DATA_PACKET = 1004,
|
||||
TCP_NODELAY_FAILED = 1005,
|
||||
TCP_NONBLOCKING_FAILED = 1006,
|
||||
CLOSE_FAILED = 1007,
|
||||
SHUTDOWN_FAILED = 1008,
|
||||
BAD_STATE = 1009,
|
||||
BAD_ARG = 1010,
|
||||
SOCKET_READ_FAILED = 1011,
|
||||
SOCKET_WRITE_FAILED = 1012,
|
||||
HANDSHAKESTATE_READ_FAILED = 1013,
|
||||
HANDSHAKESTATE_WRITE_FAILED = 1014,
|
||||
HANDSHAKESTATE_BAD_STATE = 1015,
|
||||
CIPHERSTATE_DECRYPT_FAILED = 1016,
|
||||
CIPHERSTATE_ENCRYPT_FAILED = 1017,
|
||||
OUT_OF_MEMORY = 1018,
|
||||
HANDSHAKESTATE_SETUP_FAILED = 1019,
|
||||
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
||||
};
|
||||
|
||||
const char *api_error_to_str(APIError err);
|
||||
|
||||
class APIFrameHelper {
|
||||
public:
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
virtual bool can_write_without_blocking() = 0;
|
||||
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
|
||||
virtual std::string getpeername() = 0;
|
||||
virtual APIError close() = 0;
|
||||
virtual APIError shutdown(int how) = 0;
|
||||
// Give this helper a name for logging
|
||||
virtual void set_log_info(std::string info) = 0;
|
||||
};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||
: socket_(std::move(socket)), ctx_(ctx) {}
|
||||
~APINoiseFrameHelper();
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
std::string getpeername() override { return socket_->getpeername(); }
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||
|
||||
protected:
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
APIError state_action_();
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
APIError write_frame_(const uint8_t *data, size_t len);
|
||||
APIError write_raw_(const uint8_t *data, size_t len);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
uint8_t rx_header_buf_[3];
|
||||
size_t rx_header_buf_len_ = 0;
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> tx_buf_;
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
std::shared_ptr<APINoiseContext> ctx_;
|
||||
NoiseHandshakeState *handshake_ = nullptr;
|
||||
NoiseCipherState *send_cipher_ = nullptr;
|
||||
NoiseCipherState *recv_cipher_ = nullptr;
|
||||
NoiseProtocolId nid_;
|
||||
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
CLIENT_HELLO = 2,
|
||||
SERVER_HELLO = 3,
|
||||
HANDSHAKE = 4,
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
} state_ = State::INITIALIZE;
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
|
||||
~APIPlaintextFrameHelper() = default;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
std::string getpeername() override { return socket_->getpeername(); }
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) override { info_ = std::move(info); }
|
||||
|
||||
protected:
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_send_tx_buf_();
|
||||
APIError write_raw_(const uint8_t *data, size_t len);
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
std::string info_;
|
||||
std::vector<uint8_t> rx_header_buf_;
|
||||
bool rx_header_parsed_ = false;
|
||||
uint32_t rx_header_parsed_type_ = 0;
|
||||
uint32_t rx_header_parsed_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> tx_buf_;
|
||||
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
DATA = 2,
|
||||
CLOSED = 3,
|
||||
FAILED = 4,
|
||||
} state_ = State::INITIALIZE;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
23
esphome/components/api/api_noise_context.h
Normal file
23
esphome/components/api/api_noise_context.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
using psk_t = std::array<uint8_t, 32>;
|
||||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
void set_psk(psk_t psk) { psk_ = std::move(psk); }
|
||||
const psk_t &get_psk() const { return psk_; }
|
||||
|
||||
protected:
|
||||
psk_t psk_;
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
File diff suppressed because it is too large
Load Diff
@@ -32,9 +32,22 @@ enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum ColorMode : uint32_t {
|
||||
COLOR_MODE_UNKNOWN = 0,
|
||||
COLOR_MODE_ON_OFF = 1,
|
||||
COLOR_MODE_BRIGHTNESS = 2,
|
||||
COLOR_MODE_WHITE = 7,
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11,
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19,
|
||||
COLOR_MODE_RGB = 35,
|
||||
COLOR_MODE_RGB_WHITE = 39,
|
||||
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
|
||||
COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
|
||||
};
|
||||
enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_NONE = 0,
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||
};
|
||||
enum SensorLastResetType : uint32_t {
|
||||
LAST_RESET_NONE = 0,
|
||||
@@ -46,9 +59,10 @@ enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
LOG_LEVEL_WARN = 2,
|
||||
LOG_LEVEL_INFO = 3,
|
||||
LOG_LEVEL_DEBUG = 4,
|
||||
LOG_LEVEL_VERBOSE = 5,
|
||||
LOG_LEVEL_VERY_VERBOSE = 6,
|
||||
LOG_LEVEL_CONFIG = 4,
|
||||
LOG_LEVEL_DEBUG = 5,
|
||||
LOG_LEVEL_VERBOSE = 6,
|
||||
LOG_LEVEL_VERY_VERBOSE = 7,
|
||||
};
|
||||
enum ServiceArgType : uint32_t {
|
||||
SERVICE_ARG_TYPE_BOOL = 0,
|
||||
@@ -111,7 +125,9 @@ class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -122,7 +138,9 @@ class HelloResponse : public ProtoMessage {
|
||||
uint32_t api_version_minor{0};
|
||||
std::string server_info{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -132,7 +150,9 @@ class ConnectRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string password{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -141,7 +161,9 @@ class ConnectResponse : public ProtoMessage {
|
||||
public:
|
||||
bool invalid_password{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
@@ -149,35 +171,45 @@ class ConnectResponse : public ProtoMessage {
|
||||
class DisconnectRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class DisconnectResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -193,7 +225,9 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string project_name{};
|
||||
std::string project_version{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -202,21 +236,27 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
class ListEntitiesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -228,8 +268,11 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
std::string unique_id{};
|
||||
std::string device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -242,7 +285,9 @@ class BinarySensorStateResponse : public ProtoMessage {
|
||||
bool state{false};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -258,8 +303,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
bool supports_position{false};
|
||||
bool supports_tilt{false};
|
||||
std::string device_class{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -274,7 +322,9 @@ class CoverStateResponse : public ProtoMessage {
|
||||
float tilt{0.0f};
|
||||
enums::CoverOperation current_operation{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -291,7 +341,9 @@ class CoverCommandRequest : public ProtoMessage {
|
||||
float tilt{0.0f};
|
||||
bool stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -307,8 +359,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||
bool supports_speed{false};
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -324,7 +379,9 @@ class FanStateResponse : public ProtoMessage {
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -344,7 +401,9 @@ class FanCommandRequest : public ProtoMessage {
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -356,15 +415,19 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_brightness{false};
|
||||
bool supports_rgb{false};
|
||||
bool supports_white_value{false};
|
||||
bool supports_color_temperature{false};
|
||||
std::vector<enums::ColorMode> supported_color_modes{};
|
||||
bool legacy_supports_brightness{false};
|
||||
bool legacy_supports_rgb{false};
|
||||
bool legacy_supports_white_value{false};
|
||||
bool legacy_supports_color_temperature{false};
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -376,15 +439,20 @@ class LightStateResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
float brightness{0.0f};
|
||||
enums::ColorMode color_mode{};
|
||||
float color_brightness{0.0f};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
float blue{0.0f};
|
||||
float white{0.0f};
|
||||
float color_temperature{0.0f};
|
||||
float cold_white{0.0f};
|
||||
float warm_white{0.0f};
|
||||
std::string effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -398,6 +466,8 @@ class LightCommandRequest : public ProtoMessage {
|
||||
bool state{false};
|
||||
bool has_brightness{false};
|
||||
float brightness{0.0f};
|
||||
bool has_color_mode{false};
|
||||
enums::ColorMode color_mode{};
|
||||
bool has_color_brightness{false};
|
||||
float color_brightness{0.0f};
|
||||
bool has_rgb{false};
|
||||
@@ -408,6 +478,10 @@ class LightCommandRequest : public ProtoMessage {
|
||||
float white{0.0f};
|
||||
bool has_color_temperature{false};
|
||||
float color_temperature{0.0f};
|
||||
bool has_cold_white{false};
|
||||
float cold_white{0.0f};
|
||||
bool has_warm_white{false};
|
||||
float warm_white{0.0f};
|
||||
bool has_transition_length{false};
|
||||
uint32_t transition_length{0};
|
||||
bool has_flash_length{false};
|
||||
@@ -415,7 +489,9 @@ class LightCommandRequest : public ProtoMessage {
|
||||
bool has_effect{false};
|
||||
std::string effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -434,9 +510,12 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
bool force_update{false};
|
||||
std::string device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
enums::SensorLastResetType last_reset_type{};
|
||||
enums::SensorLastResetType legacy_last_reset_type{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -449,7 +528,9 @@ class SensorStateResponse : public ProtoMessage {
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -463,8 +544,11 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool assumed_state{false};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -476,7 +560,9 @@ class SwitchStateResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -487,7 +573,9 @@ class SwitchCommandRequest : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -500,12 +588,16 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
@@ -513,7 +605,9 @@ class TextSensorStateResponse : public ProtoMessage {
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -525,7 +619,9 @@ class SubscribeLogsRequest : public ProtoMessage {
|
||||
enums::LogLevel level{};
|
||||
bool dump_config{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
@@ -533,11 +629,12 @@ class SubscribeLogsRequest : public ProtoMessage {
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{};
|
||||
std::string tag{};
|
||||
std::string message{};
|
||||
bool send_failed{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -546,7 +643,9 @@ class SubscribeLogsResponse : public ProtoMessage {
|
||||
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -555,7 +654,9 @@ class HomeassistantServiceMap : public ProtoMessage {
|
||||
std::string key{};
|
||||
std::string value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -568,7 +669,9 @@ class HomeassistantServiceResponse : public ProtoMessage {
|
||||
std::vector<HomeassistantServiceMap> variables{};
|
||||
bool is_event{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -577,7 +680,9 @@ class HomeassistantServiceResponse : public ProtoMessage {
|
||||
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -586,7 +691,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
std::string entity_id{};
|
||||
std::string attribute{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -597,7 +704,9 @@ class HomeAssistantStateResponse : public ProtoMessage {
|
||||
std::string state{};
|
||||
std::string attribute{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -605,7 +714,9 @@ class HomeAssistantStateResponse : public ProtoMessage {
|
||||
class GetTimeRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
@@ -613,7 +724,9 @@ class GetTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t epoch_seconds{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -623,7 +736,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
std::string name{};
|
||||
enums::ServiceArgType type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
@@ -635,7 +750,9 @@ class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
std::vector<ListEntitiesServicesArgument> args{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -653,7 +770,9 @@ class ExecuteServiceArgument : public ProtoMessage {
|
||||
std::vector<float> float_array{};
|
||||
std::vector<std::string> string_array{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -665,7 +784,9 @@ class ExecuteServiceRequest : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
std::vector<ExecuteServiceArgument> args{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -677,12 +798,16 @@ class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
public:
|
||||
@@ -690,7 +815,9 @@ class CameraImageResponse : public ProtoMessage {
|
||||
std::string data{};
|
||||
bool done{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -702,7 +829,9 @@ class CameraImageRequest : public ProtoMessage {
|
||||
bool single{false};
|
||||
bool stream{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
@@ -726,8 +855,11 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
std::vector<std::string> supported_custom_fan_modes{};
|
||||
std::vector<enums::ClimatePreset> supported_presets{};
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -750,7 +882,9 @@ class ClimateStateResponse : public ProtoMessage {
|
||||
enums::ClimatePreset preset{};
|
||||
std::string custom_preset{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -781,7 +915,9 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||
bool has_custom_preset{false};
|
||||
std::string custom_preset{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -798,12 +934,16 @@ class ListEntitiesNumberResponse : public ProtoMessage {
|
||||
float min_value{0.0f};
|
||||
float max_value{0.0f};
|
||||
float step{0.0f};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class NumberStateResponse : public ProtoMessage {
|
||||
public:
|
||||
@@ -811,7 +951,9 @@ class NumberStateResponse : public ProtoMessage {
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
@@ -822,11 +964,60 @@ class NumberCommandRequest : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
float state{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesSelectResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
std::vector<std::string> options{};
|
||||
bool disabled_by_default{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SelectStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SelectCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -9,58 +9,82 @@ namespace api {
|
||||
static const char *const TAG = "api.service";
|
||||
|
||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<HelloResponse>(msg, 2);
|
||||
}
|
||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ConnectResponse>(msg, 4);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<PingRequest>(msg, 7);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<PingResponse>(msg, 8);
|
||||
}
|
||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||
}
|
||||
#endif
|
||||
@@ -68,13 +92,17 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<FanStateResponse>(msg, 23);
|
||||
}
|
||||
#endif
|
||||
@@ -82,13 +110,17 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<LightStateResponse>(msg, 24);
|
||||
}
|
||||
#endif
|
||||
@@ -96,25 +128,33 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||
}
|
||||
#endif
|
||||
@@ -122,13 +162,17 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||
}
|
||||
#endif
|
||||
@@ -136,35 +180,49 @@ bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsRe
|
||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||
}
|
||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||
}
|
||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||
const SubscribeHomeAssistantStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||
}
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||
}
|
||||
#endif
|
||||
@@ -172,13 +230,17 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||
}
|
||||
#endif
|
||||
@@ -186,87 +248,129 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesNumberResponse>(msg, 49);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<NumberStateResponse>(msg, 50);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool APIServerConnectionBase::send_list_entities_select_response(const ListEntitiesSelectResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_select_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesSelectResponse>(msg, 52);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool APIServerConnectionBase::send_select_state_response(const SelectStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_select_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<SelectStateResponse>(msg, 53);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
HelloRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
ConnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_connect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
DisconnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_disconnect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
DisconnectResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_disconnect_response(msg);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
PingRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_ping_request(msg);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
PingResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_ping_response(msg);
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
DeviceInfoRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_device_info_request(msg);
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
ListEntitiesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_list_entities_request(msg);
|
||||
break;
|
||||
}
|
||||
case 20: {
|
||||
SubscribeStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 28: {
|
||||
SubscribeLogsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_logs_request(msg);
|
||||
break;
|
||||
}
|
||||
@@ -274,7 +378,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_cover_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -283,7 +389,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_fan_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -292,7 +400,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_light_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -301,7 +411,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_switch_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -309,42 +421,54 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
case 34: {
|
||||
SubscribeHomeassistantServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_homeassistant_services_request(msg);
|
||||
break;
|
||||
}
|
||||
case 36: {
|
||||
GetTimeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_get_time_request(msg);
|
||||
break;
|
||||
}
|
||||
case 37: {
|
||||
GetTimeResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_get_time_response(msg);
|
||||
break;
|
||||
}
|
||||
case 38: {
|
||||
SubscribeHomeAssistantStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_home_assistant_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
HomeAssistantStateResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_home_assistant_state_response(msg);
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
ExecuteServiceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_execute_service_request(msg);
|
||||
break;
|
||||
}
|
||||
@@ -352,7 +476,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_camera_image_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -361,7 +487,9 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_climate_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
@@ -370,8 +498,21 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
#ifdef USE_NUMBER
|
||||
NumberCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_number_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 54: {
|
||||
#ifdef USE_SELECT
|
||||
SelectCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_select_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -583,6 +724,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
|
||||
this->number_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->select_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -120,6 +120,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state_response(const SelectStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
@@ -159,6 +168,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual void number_command(const NumberCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual void select_command(const SelectCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -194,6 +206,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_command_request(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_command_request(const SelectCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
@@ -1,10 +1,11 @@
|
||||
#include "api_server.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include <cerrno>
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
@@ -21,20 +22,45 @@ static const char *const TAG = "api";
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||
this->setup_controller();
|
||||
this->server_ = AsyncServer(this->port_);
|
||||
this->server_.setNoDelay(false);
|
||||
this->server_.begin();
|
||||
this->server_.onClient(
|
||||
[](void *s, AsyncClient *client) {
|
||||
if (client == nullptr)
|
||||
return;
|
||||
socket_ = socket::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
||||
// we can still continue
|
||||
}
|
||||
err = socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in server;
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
|
||||
server.sin_port = htons(this->port_);
|
||||
|
||||
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->listen(4);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// can't print here because in lwIP thread
|
||||
// ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str());
|
||||
auto *a_this = (APIServer *) s;
|
||||
a_this->clients_.push_back(new APIConnection(client, a_this));
|
||||
},
|
||||
this);
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
@@ -59,12 +85,26 @@ void APIServer::setup() {
|
||||
#endif
|
||||
}
|
||||
void APIServer::loop() {
|
||||
// Accept new clients
|
||||
while (true) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
clients_.push_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
|
||||
// Partition clients into remove and active
|
||||
auto new_end =
|
||||
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
|
||||
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// only then delete the pointers, otherwise log routine
|
||||
// would access freed memory
|
||||
@@ -206,6 +246,15 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_select_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
@@ -4,20 +4,14 @@
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "util.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "homeassistant_service.h"
|
||||
#include "user_services.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
#include "api_noise_context.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -36,6 +30,12 @@ class APIServer : public Component, public Controller {
|
||||
void set_port(uint16_t port);
|
||||
void set_password(const std::string &password);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); }
|
||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
@@ -63,6 +63,9 @@ class APIServer : public Component, public Controller {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
@@ -84,7 +87,7 @@ class APIServer : public Component, public Controller {
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
|
||||
protected:
|
||||
AsyncServer server_{0};
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
uint16_t port_{6053};
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
@@ -92,6 +95,10 @@ class APIServer : public Component, public Controller {
|
||||
std::string password_;
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||
#endif // USE_API_NOISE
|
||||
};
|
||||
|
||||
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
@@ -55,5 +55,9 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -42,6 +42,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
|
@@ -80,11 +80,13 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string ProtoMessage::dump() const {
|
||||
std::string out;
|
||||
this->dump_to(out);
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -1,8 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
#define HAS_PROTO_MESSAGE_DUMP
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
@@ -243,8 +248,10 @@ class ProtoMessage {
|
||||
public:
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
|
||||
|
@@ -42,6 +42,11 @@ bool InitialStateIterator::on_number(number::Number *number) {
|
||||
return this->client_->send_number_state(number, number->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool InitialStateIterator::on_select(select::Select *select) {
|
||||
return this->client_->send_select_state(select, select->state);
|
||||
}
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
|
@@ -39,6 +39,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
@@ -15,7 +15,7 @@ template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceAr
|
||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.bool_array;
|
||||
}
|
||||
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
|
||||
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.int_array;
|
||||
}
|
||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||
|
@@ -182,6 +182,21 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
case IteratorState::SELECT:
|
||||
if (this->at_ >= App.get_selects().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *select = App.get_selects()[this->at_];
|
||||
if (select->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_select(select);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
|
@@ -50,6 +50,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
virtual bool on_number(number::Number *number) = 0;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual bool on_select(select::Select *select) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
@@ -87,6 +90,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
NUMBER,
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SELECT,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
|
@@ -4,10 +4,8 @@ from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_DISTANCE,
|
||||
CONF_LIGHTNING_ENERGY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_KILOMETER,
|
||||
UNIT_EMPTY,
|
||||
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
ICON_FLASH,
|
||||
)
|
||||
@@ -19,14 +17,15 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
UNIT_KILOMETER,
|
||||
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
unit_of_measurement=UNIT_KILOMETER,
|
||||
icon=ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
@@ -12,7 +12,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -34,28 +33,28 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -39,7 +39,7 @@ static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage
|
||||
static const uint16_t ATM90E32_STATUS_S0_OVPHASECST = 1 << 10; // Over voltage on phase C
|
||||
static const uint16_t ATM90E32_STATUS_S0_UREVWNST = 1 << 9; // Voltage Phase Sequence Error status
|
||||
static const uint16_t ATM90E32_STATUS_S0_IREVWNST = 1 << 8; // Current Phase Sequence Error status
|
||||
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater tha INWarnTh reg
|
||||
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater than INWarnTh reg
|
||||
static const uint16_t ATM90E32_STATUS_S0_TQNOLOADST = 1 << 6; // All phase sum reactive power no-load condition status
|
||||
static const uint16_t ATM90E32_STATUS_S0_TPNOLOADST = 1 << 5; // All phase sum active power no-load condition status
|
||||
static const uint16_t ATM90E32_STATUS_S0_TASNOLOADST = 1 << 4; // All phase sum apparent power no-load status
|
||||
|
@@ -12,22 +12,19 @@ from esphome.const import (
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_CURRENT_AC,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
UNIT_EMPTY,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
UNIT_WATT_HOURS,
|
||||
@@ -65,47 +62,45 @@ ATM90E32Component = atm90e32_ns.class_(
|
||||
ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
ICON_LIGHTBULB,
|
||||
2,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||
icon=ICON_LIGHTBULB,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
@@ -120,18 +115,16 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
UNIT_HERTZ,
|
||||
ICON_CURRENT_AC,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
|
||||
|
@@ -47,7 +47,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
uint16_t battery_millivolt = data[2] << 8 | data[3];
|
||||
float battery_voltage = battery_millivolt / 1000.0f;
|
||||
|
||||
// Temperature in 1000 * Celcius.
|
||||
// Temperature in 1000 * Celsius.
|
||||
uint16_t temp_millicelcius = data[4] << 8 | data[5];
|
||||
float temp_celcius = temp_millicelcius / 1000.0f;
|
||||
|
||||
|
@@ -11,7 +11,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -33,28 +32,28 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(): cv.declare_id(BParasite),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -5,7 +5,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_LUX,
|
||||
CONF_MEASUREMENT_DURATION,
|
||||
@@ -28,7 +27,10 @@ BH1750Sensor = bh1750_ns.class_(
|
||||
CONF_MEASUREMENT_TIME = "measurement_time"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
|
@@ -55,7 +55,10 @@ void BinaryFan::loop() {
|
||||
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
|
||||
}
|
||||
}
|
||||
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
// We need a higher priority than the FanState component to make sure that the traits are set
|
||||
// when that component sets itself up.
|
||||
float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; }
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
|
@@ -12,7 +12,7 @@ class BinaryLightOutput : public light::LightOutput {
|
||||
void set_output(output::BinaryOutput *output) { output_ = output; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(false);
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
|
@@ -6,6 +6,7 @@ from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FILTERS,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
@@ -47,6 +48,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
DEVICE_CLASS_SOUND,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
@@ -78,6 +80,7 @@ DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
DEVICE_CLASS_SOUND,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
]
|
||||
@@ -315,7 +318,7 @@ def validate_multi_click_timing(value):
|
||||
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
@@ -377,6 +380,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
|
@@ -7,8 +7,6 @@ from esphome.const import (
|
||||
CONF_CHANNELS,
|
||||
CONF_VALUE,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
UNIT_EMPTY,
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_GROUP,
|
||||
@@ -35,11 +33,9 @@ entry = {
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_GROUP: sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
icon=ICON_CHECK_CIRCLE_OUTLINE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
||||
|
@@ -25,7 +25,7 @@ class BLEClientNode {
|
||||
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;
|
||||
virtual void loop() = 0;
|
||||
virtual void loop(){};
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
espbt::ESPBTClient *client;
|
||||
// This should be transitioned to Established once the node no longer needs
|
||||
|
@@ -2,12 +2,9 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_EMPTY,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SERVICE_UUID,
|
||||
)
|
||||
@@ -34,7 +31,8 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
|
@@ -1,7 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
|
||||
from esphome.const import (
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_SERVICE_UUID,
|
||||
CONF_IBEACON_MAJOR,
|
||||
CONF_IBEACON_MINOR,
|
||||
CONF_IBEACON_UUID,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
@@ -13,17 +20,30 @@ BLEPresenceDevice = ble_presence_ns.class_(
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
)
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config:
|
||||
raise cv.Invalid("iBeacon major identifier requires iBeacon UUID")
|
||||
if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config:
|
||||
raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_UUID): cv.uuid,
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
|
||||
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
@@ -50,5 +70,15 @@ async def to_code(config):
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if CONF_IBEACON_UUID in config:
|
||||
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID]))
|
||||
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
|
||||
|
||||
if CONF_IBEACON_MAJOR in config:
|
||||
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR]))
|
||||
|
||||
if CONF_IBEACON_MINOR in config:
|
||||
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR]))
|
||||
|
@@ -14,41 +14,78 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) {
|
||||
this->by_address_ = true;
|
||||
this->match_by_ = MATCH_BY_MAC_ADDRESS;
|
||||
this->address_ = address;
|
||||
}
|
||||
void set_service_uuid16(uint16_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||
}
|
||||
void set_service_uuid32(uint32_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||
}
|
||||
void set_service_uuid128(uint8_t *uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
void set_ibeacon_uuid(uint8_t *uuid) {
|
||||
this->match_by_ = MATCH_BY_IBEACON_UUID;
|
||||
this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
void set_ibeacon_major(uint16_t major) {
|
||||
this->check_ibeacon_major_ = true;
|
||||
this->ibeacon_major_ = major;
|
||||
}
|
||||
void set_ibeacon_minor(uint16_t minor) {
|
||||
this->check_ibeacon_minor_ = true;
|
||||
this->ibeacon_minor_ = minor;
|
||||
}
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(false);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (this->by_address_) {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
this->publish_state(device.get_rssi());
|
||||
switch (this->match_by_) {
|
||||
case MATCH_BY_MAC_ADDRESS:
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_SERVICE_UUID:
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_IBEACON_UUID:
|
||||
if (!device.get_ibeacon().has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ibeacon = device.get_ibeacon().value();
|
||||
|
||||
if (this->ibeacon_uuid_ != ibeacon.get_uuid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -56,10 +93,20 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MATCH_TYPE match_by_;
|
||||
|
||||
bool found_{false};
|
||||
bool by_address_{false};
|
||||
|
||||
uint64_t address_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
|
||||
uint16_t ibeacon_major_;
|
||||
bool check_ibeacon_major_;
|
||||
uint16_t ibeacon_minor_;
|
||||
bool check_ibeacon_minor_;
|
||||
};
|
||||
|
||||
} // namespace ble_presence
|
||||
|
@@ -8,7 +8,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL,
|
||||
ICON_EMPTY,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
@@ -20,11 +19,10 @@ BLERSSISensor = ble_rssi_ns.class_(
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
UNIT_DECIBEL,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_DECIBEL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
@@ -62,5 +60,5 @@ async def to_code(config):
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
@@ -11,7 +11,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
@@ -49,11 +48,10 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -62,11 +60,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -75,11 +72,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
|
@@ -12,7 +12,6 @@ from esphome.const import (
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
@@ -20,7 +19,6 @@ from esphome.const import (
|
||||
UNIT_OHM,
|
||||
ICON_GAS_CYLINDER,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
@@ -59,11 +57,10 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -72,11 +69,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -85,11 +81,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -98,11 +93,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||
UNIT_OHM,
|
||||
ICON_GAS_CYLINDER,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_OHM,
|
||||
icon=ICON_GAS_CYLINDER,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
|
@@ -10,7 +10,7 @@ static const char *const TAG = "bme680_bsec.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
BME680BSECComponent *BME680BSECComponent::instance;
|
||||
BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void BME680BSECComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
|
||||
@@ -359,7 +359,7 @@ void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float va
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) {
|
||||
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) {
|
||||
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
int64_t get_time_ns_();
|
||||
|
||||
void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value);
|
||||
void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||
|
||||
void load_state_();
|
||||
void save_state_(uint8_t accuracy);
|
||||
|
@@ -6,13 +6,11 @@ from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
@@ -54,54 +52,60 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_GAUGE,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_WATER_PERCENT,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_WATER_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||
UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_OHM,
|
||||
icon=ICON_GAS_CYLINDER,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_IAQ): sensor.sensor_schema(
|
||||
UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_IAQ,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
icon=ICON_ACCURACY,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_TEST_TUBE,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_TEST_TUBE,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_TEST_TUBE,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_TEST_TUBE,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
)
|
||||
|
||||
@@ -25,18 +24,16 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP085Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
@@ -46,11 +45,10 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -59,11 +57,10 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
|
@@ -16,7 +16,7 @@ static const char *const TAG = "ccs811";
|
||||
return; \
|
||||
}
|
||||
|
||||
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED)
|
||||
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICATION_FAILED)
|
||||
|
||||
void CCS811Component::setup() {
|
||||
// page 9 programming guide - hwid is always 0x81
|
||||
@@ -38,12 +38,14 @@ void CCS811Component::setup() {
|
||||
// set MEAS_MODE (page 5)
|
||||
uint8_t meas_mode = 0;
|
||||
uint32_t interval = this->get_update_interval();
|
||||
if (interval <= 1000)
|
||||
meas_mode = 1 << 4;
|
||||
else if (interval <= 10000)
|
||||
meas_mode = 2 << 4;
|
||||
if (interval >= 60 * 1000)
|
||||
meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
|
||||
else if (interval >= 10 * 1000)
|
||||
meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
|
||||
else if (interval >= 1 * 1000)
|
||||
meas_mode = 1 << 4; // sensor takes a reading every second
|
||||
else
|
||||
meas_mode = 3 << 4;
|
||||
meas_mode = 4 << 4; // sensor takes a reading every 250ms
|
||||
|
||||
CHECKED_IO(this->write_byte(0x01, meas_mode))
|
||||
|
||||
@@ -51,6 +53,36 @@ void CCS811Component::setup() {
|
||||
// baseline available, write to sensor
|
||||
this->write_bytes(0x11, decode_uint16(*this->baseline_));
|
||||
}
|
||||
|
||||
auto hardware_version_data = this->read_bytes<1>(0x21);
|
||||
auto bootloader_version_data = this->read_bytes<2>(0x23);
|
||||
auto application_version_data = this->read_bytes<2>(0x24);
|
||||
|
||||
uint8_t hardware_version = 0;
|
||||
uint16_t bootloader_version = 0;
|
||||
uint16_t application_version = 0;
|
||||
|
||||
if (hardware_version_data.has_value()) {
|
||||
hardware_version = (*hardware_version_data)[0];
|
||||
}
|
||||
|
||||
if (bootloader_version_data.has_value()) {
|
||||
bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]);
|
||||
}
|
||||
|
||||
if (application_version_data.has_value()) {
|
||||
application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version,
|
||||
bootloader_version, application_version);
|
||||
if (this->version_ != nullptr) {
|
||||
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
|
||||
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
|
||||
(application_version >> 4 & 15), application_version);
|
||||
ESP_LOGD(TAG, "publishing version state: %s", version);
|
||||
this->version_->publish_state(version);
|
||||
}
|
||||
}
|
||||
void CCS811Component::update() {
|
||||
if (!this->status_has_data_())
|
||||
@@ -117,6 +149,7 @@ void CCS811Component::dump_config() {
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
||||
if (this->baseline_) {
|
||||
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
||||
} else {
|
||||
@@ -124,7 +157,7 @@ void CCS811Component::dump_config() {
|
||||
}
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICAITON_FAILED:
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||
break;
|
||||
case INVALID_ID:
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -12,6 +13,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
|
||||
void set_version(text_sensor::TextSensor *version) { version_ = version; }
|
||||
void set_baseline(uint16_t baseline) { baseline_ = baseline; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
@@ -34,7 +36,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
|
||||
|
||||
enum ErrorCode {
|
||||
UNKNOWN,
|
||||
COMMUNICAITON_FAILED,
|
||||
COMMUNICATION_FAILED,
|
||||
INVALID_ID,
|
||||
SENSOR_REPORTED_ERROR,
|
||||
APP_INVALID,
|
||||
@@ -43,6 +45,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
|
||||
|
||||
sensor::Sensor *co2_{nullptr};
|
||||
sensor::Sensor *tvoc_{nullptr};
|
||||
text_sensor::TextSensor *version_{nullptr};
|
||||
optional<uint16_t> baseline_{};
|
||||
/// Input sensor for humidity reading.
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
ICON_RADIATOR,
|
||||
ICON_RESTART,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
@@ -13,9 +16,12 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TVOC,
|
||||
CONF_HUMIDITY,
|
||||
CONF_VERSION,
|
||||
ICON_MOLECULE_CO2,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["text_sensor"]
|
||||
CODEOWNERS = ["@habbie"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ccs811_ns = cg.esphome_ns.namespace("ccs811")
|
||||
@@ -28,18 +34,24 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_MOLECULE_CO2,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
@@ -61,6 +73,11 @@ async def to_code(config):
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if CONF_VERSION in config:
|
||||
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
cg.add(var.set_baseline(config[CONF_BASELINE]))
|
||||
|
||||
|
@@ -6,6 +6,7 @@ from esphome.const import (
|
||||
CONF_AWAY,
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
CONF_CUSTOM_PRESET,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
@@ -62,6 +63,7 @@ validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
|
||||
ClimatePreset = climate_ns.enum("ClimatePreset")
|
||||
CLIMATE_PRESETS = {
|
||||
"NONE": ClimatePreset.CLIMATE_PRESET_NONE,
|
||||
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
|
||||
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
|
||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
||||
@@ -86,7 +88,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
|
||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
@@ -104,6 +106,7 @@ CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
|
||||
async def setup_climate_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
visual = config[CONF_VISUAL]
|
||||
|
@@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
|
||||
this->rtc_ =
|
||||
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
|
||||
ClimateDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
@@ -490,5 +494,74 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||
climate->publish_state();
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> bool set_alternative(optional<T1> &dst, optional<T2> &alt, const T1 &src) {
|
||||
bool is_changed = alt.has_value();
|
||||
alt.reset();
|
||||
if (is_changed || dst != src) {
|
||||
dst = src;
|
||||
is_changed = true;
|
||||
}
|
||||
return is_changed;
|
||||
}
|
||||
|
||||
bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
||||
return set_alternative(this->fan_mode, this->custom_fan_mode, mode);
|
||||
}
|
||||
|
||||
bool Climate::set_custom_fan_mode_(const std::string &mode) {
|
||||
return set_alternative(this->custom_fan_mode, this->fan_mode, mode);
|
||||
}
|
||||
|
||||
bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); }
|
||||
|
||||
bool Climate::set_custom_preset_(const std::string &preset) {
|
||||
return set_alternative(this->custom_preset, this->preset, preset);
|
||||
}
|
||||
|
||||
void Climate::dump_traits_(const char *tag) {
|
||||
auto traits = this->get_traits();
|
||||
ESP_LOGCONFIG(tag, "ClimateTraits:");
|
||||
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
||||
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step());
|
||||
if (traits.get_supports_current_temperature())
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||
if (traits.get_supports_two_point_target_temperature())
|
||||
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
||||
if (traits.get_supports_action())
|
||||
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||
if (!traits.get_supported_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported modes:");
|
||||
for (ClimateMode m : traits.get_supported_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m));
|
||||
}
|
||||
if (!traits.get_supported_fan_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported fan modes:");
|
||||
for (ClimateFanMode m : traits.get_supported_fan_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:");
|
||||
for (const std::string &s : traits.get_supported_custom_fan_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
||||
}
|
||||
if (!traits.get_supported_presets().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported presets:");
|
||||
for (ClimatePreset p : traits.get_supported_presets())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported custom presets:");
|
||||
for (const std::string &s : traits.get_supported_custom_presets())
|
||||
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
||||
}
|
||||
if (!traits.get_supported_swing_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported swing modes:");
|
||||
for (ClimateSwingMode m : traits.get_supported_swing_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
@@ -63,9 +63,9 @@ class ClimateCall {
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
|
||||
ClimateCall &set_away(bool away);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead")
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
|
||||
ClimateCall &set_away(optional<bool> away);
|
||||
/// Set the fan mode of the climate device.
|
||||
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
|
||||
@@ -96,7 +96,7 @@ class ClimateCall {
|
||||
const optional<float> &get_target_temperature() const;
|
||||
const optional<float> &get_target_temperature_low() const;
|
||||
const optional<float> &get_target_temperature_high() const;
|
||||
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead")
|
||||
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
|
||||
optional<bool> get_away() const;
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
@@ -120,6 +120,7 @@ class ClimateCall {
|
||||
};
|
||||
|
||||
/// Struct used to save the state of the climate device in restore memory.
|
||||
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
|
||||
struct ClimateDeviceRestoreState {
|
||||
ClimateMode mode;
|
||||
bool uses_custom_fan_mode{false};
|
||||
@@ -192,7 +193,7 @@ class Climate : public Nameable {
|
||||
* Away allows climate devices to have two different target temperature configs:
|
||||
* one for normal mode and one for away mode.
|
||||
*/
|
||||
ESPDEPRECATED("away is deprecated, use preset instead")
|
||||
ESPDEPRECATED("away is deprecated, use preset instead", "v1.20")
|
||||
bool away{false};
|
||||
|
||||
/// The active fan mode of the climate device.
|
||||
@@ -244,6 +245,18 @@ class Climate : public Nameable {
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
|
||||
/// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed.
|
||||
bool set_fan_mode_(ClimateFanMode mode);
|
||||
|
||||
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
|
||||
bool set_custom_fan_mode_(const std::string &mode);
|
||||
|
||||
/// Set preset. Reset custom preset. Return true if preset has been changed.
|
||||
bool set_preset_(ClimatePreset preset);
|
||||
|
||||
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
|
||||
bool set_custom_preset_(const std::string &preset);
|
||||
|
||||
/** Get the default traits of this climate device.
|
||||
*
|
||||
* Traits are static data that encode the capabilities and static data for a climate device such as supported
|
||||
@@ -269,6 +282,7 @@ class Climate : public Nameable {
|
||||
void save_state_();
|
||||
|
||||
uint32_t hash_base() override;
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
|
@@ -50,19 +50,19 @@ class ClimateTraits {
|
||||
}
|
||||
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_fan_only_mode(bool supports_fan_only_mode) {
|
||||
set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode);
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
|
||||
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
|
||||
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
|
||||
@@ -72,23 +72,24 @@ class ClimateTraits {
|
||||
|
||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
|
||||
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
|
||||
@@ -104,6 +105,7 @@ class ClimateTraits {
|
||||
|
||||
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
|
||||
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
|
||||
void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); }
|
||||
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
|
||||
bool get_supports_presets() const { return !supported_presets_.empty(); }
|
||||
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
|
||||
@@ -115,25 +117,25 @@ class ClimateTraits {
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return supported_custom_presets_.count(custom_preset);
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
|
||||
void set_supports_away(bool supports) {
|
||||
if (supports) {
|
||||
supported_presets_.insert(CLIMATE_PRESET_AWAY);
|
||||
supported_presets_.insert(CLIMATE_PRESET_HOME);
|
||||
}
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use supports_preset() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
|
||||
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
|
||||
|
||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
|
||||
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead")
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
void set_supports_swing_mode_horizontal(bool supported) {
|
||||
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
|
||||
}
|
||||
|
0
esphome/components/color_temperature/__init__.py
Normal file
0
esphome/components/color_temperature/__init__.py
Normal file
38
esphome/components/color_temperature/ct_light_output.h
Normal file
38
esphome/components/color_temperature/ct_light_output.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace color_temperature {
|
||||
|
||||
class CTLightOutput : public light::LightOutput {
|
||||
public:
|
||||
void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; }
|
||||
void set_brightness(output::FloatOutput *brightness) { brightness_ = brightness; }
|
||||
void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; }
|
||||
void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE});
|
||||
traits.set_min_mireds(this->cold_white_temperature_);
|
||||
traits.set_max_mireds(this->warm_white_temperature_);
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
float color_temperature, brightness;
|
||||
state->current_values_as_ct(&color_temperature, &brightness);
|
||||
this->color_temperature_->set_level(color_temperature);
|
||||
this->brightness_->set_level(brightness);
|
||||
}
|
||||
|
||||
protected:
|
||||
output::FloatOutput *color_temperature_;
|
||||
output::FloatOutput *brightness_;
|
||||
float cold_white_temperature_;
|
||||
float warm_white_temperature_;
|
||||
};
|
||||
|
||||
} // namespace color_temperature
|
||||
} // namespace esphome
|
42
esphome/components/color_temperature/light.py
Normal file
42
esphome/components/color_temperature/light.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import light, output
|
||||
from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
color_temperature_ns = cg.esphome_ns.namespace("color_temperature")
|
||||
CTLightOutput = color_temperature_ns.class_("CTLightOutput", light.LightOutput)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
light.RGB_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CTLightOutput),
|
||||
cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_BRIGHTNESS): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
}
|
||||
),
|
||||
light.validate_color_temperature_channels,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await light.register_light(var, config)
|
||||
|
||||
color_temperature = await cg.get_variable(config[CONF_COLOR_TEMPERATURE])
|
||||
cg.add(var.set_color_temperature(color_temperature))
|
||||
|
||||
brightness = await cg.get_variable(config[CONF_BRIGHTNESS])
|
||||
cg.add(var.set_brightness(brightness))
|
||||
|
||||
cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]))
|
||||
cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]))
|
@@ -4,6 +4,7 @@ from esphome import automation
|
||||
from esphome.automation import maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -63,7 +64,7 @@ CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
|
||||
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
|
||||
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
|
||||
|
||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
@@ -75,6 +76,7 @@ COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
|
||||
async def setup_cover_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
|
@@ -125,16 +125,19 @@ class Cover : public Nameable {
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
|
||||
void open();
|
||||
/** Close the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
|
||||
void close();
|
||||
/** Stop the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9")
|
||||
void stop();
|
||||
|
||||
void add_on_state_callback(std::function<void()> &&f);
|
||||
|
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
ICON_EMPTY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
@@ -80,13 +79,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_VOLTAGE_HPF, default=True): cv.boolean,
|
||||
cv.Optional(CONF_PULSE_ENERGY, default=10.0): validate_energy,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_VOLTAGE
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
@@ -28,17 +27,22 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CSE7766Component),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user