Compare commits

..

313 Commits

Author SHA1 Message Date
Jesse Hills
a82d8ea0c3 Merge pull request #3381 from esphome/bump-2022.4.0b1
2022.4.0b1
2022-04-13 16:14:08 +12:00
Jesse Hills
ad57faa9a9 Bump version to 2022.4.0b1 2022-04-13 13:42:28 +12:00
Jesse Hills
a9b5e8d036 Merge branch 'dev' into bump-2022.4.0b1 2022-04-13 13:42:27 +12:00
Jesse Hills
8be704e591 Allow specifying deep sleep wakup clock time (#3312) 2022-04-13 12:55:26 +12:00
Jesse Hills
b622a8fa58 Move PN532OnTagTrigger to nfc::NfcOnTagTrigger (#3379) 2022-04-13 12:26:55 +12:00
Jesse Hills
a519e5c475 Fix silent config errors (#3380) 2022-04-13 12:26:25 +12:00
Martin
d620b6dd5e Refactor Sensirion Sensors (#3374)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-13 10:19:48 +12:00
Janez Troha
99335d986e Use correct http defines (#3378)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-13 10:14:21 +12:00
cvwillegen
7895cd92cd Remote base pronto receive (#2826)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-13 07:39:38 +12:00
anatoly-savchenkov
8b2c032da6 Add Sonoff D1 Dimmer support (#2775)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-12 17:03:32 +12:00
Jesse Hills
da336247eb Add Xiaomi RTCGQ02LM - Mi Motion Sensor 2 (#3186) 2022-04-12 16:19:16 +12:00
andrewpc
dabd27d4be Addition of Deep Sleep RTC pin definition for ESP32-S2 (#3303) 2022-04-12 12:45:54 +12:00
functionpointer
fdda47db6e Add integration hydreon_rgxx for rain sensors by Hydreon (#2711)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-11 14:50:56 +12:00
Jesse Hills
efa6fd03e5 Make home_assistant imported sensors internal by default (#3372) 2022-04-11 12:45:15 +12:00
rrooggiieerr
9e3e34acf5 Add cover toggle support to endstop cover (#3358) 2022-04-11 10:55:45 +12:00
calco88
a2d0c1bf18 Fix HM3301 AQI int8 overflow (#3361) 2022-04-11 10:14:53 +12:00
dependabot[bot]
7663716ae8 Bump pylint from 2.13.4 to 2.13.5 (#3363)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 09:23:47 +12:00
dependabot[bot]
c2cacb3478 Bump voluptuous from 0.13.0 to 0.13.1 (#3364)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 09:23:13 +12:00
dependabot[bot]
84666b54b9 Bump pyupgrade from 2.31.1 to 2.32.0 (#3366)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 09:22:54 +12:00
RadekHvizdos
2b91c23bf3 Extend mcp3204 to support 8 channels (mcp3208 variant) (#3332) 2022-04-11 08:44:11 +12:00
Samuel Sieb
3297267a16 Fix SHTC3 sensor detection (#3365)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2022-04-11 08:42:31 +12:00
Keilin Bickar
a9e653724c Add parameter to control i2c stop signal at endTransmission (#3370) 2022-04-11 08:38:29 +12:00
djwlindenaar
5e79a1f500 Implement newer RTU protocol of Growatt inverters (#3315)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Daniel Lindenaar <daniel-git@lindenaar.eu>
2022-04-11 08:06:11 +12:00
Tim Smeets
d4ff98680a Add support for Electrolux heatpump and bump arduino-heatpumpir version (#3353)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-08 08:04:00 +12:00
Jesse Hills
ba8d255cb4 Allow on_value_range for sensor and number to be templated (#3359) 2022-04-05 22:06:36 +12:00
dependabot[bot]
06f4ad922c Bump black from 22.1.0 to 22.3.0 (#3357)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2022-04-05 11:50:51 +02:00
dependabot[bot]
bff06e448b Bump pyupgrade from 2.31.0 to 2.31.1 (#3292)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
2022-04-04 20:06:42 +02:00
dependabot[bot]
d48ffa2913 Bump tzlocal from 4.1 to 4.2 (#3356)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:39:45 +02:00
dependabot[bot]
d97c3a7e01 Bump voluptuous from 0.12.2 to 0.13.0 (#3355)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:37:35 +02:00
Otto Winter
0b1161f7ef Bump docker dependencies (#3354) 2022-04-04 19:21:43 +02:00
dependabot[bot]
061e1a471d Bump pytest from 7.0.1 to 7.1.1 (#3313)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:13:11 +02:00
dependabot[bot]
a39d874600 Bump pytest-asyncio from 0.18.2 to 0.18.3 (#3335)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:13:06 +02:00
dependabot[bot]
de96376565 Bump pylint from 2.12.2 to 2.13.4 (#3348)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:11:04 +02:00
dependabot[bot]
c54c20ab3c Bump click from 8.0.4 to 8.1.2 (#3351)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 19:10:53 +02:00
Michiel van Turnhout
70fafa473b Tm1637 binarysensor (#2792)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-04 11:42:10 +12:00
Felix Storm
2e436eae6b CAN bus: support remote transmission request flag for canbus.send (#3193)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-04 11:15:51 +12:00
Andrew J.Swan
fd7e861ff5 Added a function to load custom characters in LCD display (#3279)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-04-04 11:13:59 +12:00
Martin
792108686c Add mqtt for idf (#2930)
Co-authored-by: Flaviu Tamas <me@flaviutamas.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-04-04 11:07:20 +12:00
Branden Cash
fa1b5117fd feat: support ble_client that use security w/o pin codes (#3320) 2022-04-04 09:35:48 +12:00
Adrián Panella
b0bd9e0a34 protobuf: fix incomplete 64 bits implementation (#3341) 2022-04-04 08:38:44 +12:00
Guillermo Ruffino
05dc97099a New vscode schema gen (#3336) 2022-04-03 19:30:22 +12:00
Ian Reinhart Geiser
9de61fcf58 Define touchscreen support when in use. (#3296) 2022-04-01 16:46:39 +13:00
Jesse Hills
7f7175b184 Publish custom data when modbus number lambda fills vector (#3295) 2022-03-29 22:22:11 +13:00
Dan Jackson
cf5c640ae4 Change beginning of file comments to avoid creating doxygen tag for esphome namespace (#3314) 2022-03-29 22:05:38 +13:00
Jesse Hills
6b9371d105 Actually increase request memory for json parsing (#3331) 2022-03-28 17:04:25 +13:00
Otto Winter
9a82057303 Font allow using google fonts directly (#3243) 2022-03-28 12:07:48 +13:00
Stanislav Meduna
48584e94c4 Allow to set user defined characters on LCD (#3322) 2022-03-24 19:37:48 +13:00
dependabot[bot]
d8024a5928 Bump esptool from 3.2 to 3.3 (#3327)
Bumps [esptool](https://github.com/espressif/esptool) from 3.2 to 3.3.
- [Release notes](https://github.com/espressif/esptool/releases)
- [Commits](https://github.com/espressif/esptool/compare/v3.2...v3.3)

---
updated-dependencies:
- dependency-name: esptool
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-24 14:29:43 +13:00
H. Árkosi Róbert
2034ab4f6c increase delay for Ethernet module warm up (#3326) 2022-03-24 14:28:21 +13:00
Jesse Hills
58b70b42dd Add small delay before setting up app in safe mode (#3323) 2022-03-23 11:12:22 +13:00
Jesse Hills
1496bc1b07 Reserve less memory for json (#3289) 2022-03-23 09:46:25 +13:00
Jesse Hills
bfbf88b2ea Webserver utilize Component Iterator to not overload eventstream (#3310) 2022-03-23 09:45:05 +13:00
wysiwyng
e621b938e3 Fix WDT reset during dallas search algorithm (#3293) 2022-03-16 20:33:05 +01:00
Jesse Hills
59e6e798dd Merge pull request #3302 from esphome/bump-2022.3.0b2
2022.3.0b2
2022-03-16 15:38:32 +13:00
Jesse Hills
e5c2dbc7ec Bump version to 2022.3.0b2 2022-03-16 14:06:00 +13:00
Jesse Hills
756f71c382 Allow custom register type for modbus number (#3202) 2022-03-16 14:06:00 +13:00
Jesse Hills
b7535693fa Add helper overloads for hex print 16-bit (#3297) 2022-03-16 14:06:00 +13:00
stegm
06a3505698 Add optimistic config flag to modbus select. (#3267) 2022-03-16 14:06:00 +13:00
Jesse Hills
0372d17a11 Allow custom register type for modbus number (#3202) 2022-03-16 13:43:05 +13:00
Jesse Hills
4525588116 Add helper overloads for hex print 16-bit (#3297) 2022-03-16 13:35:37 +13:00
rbaron
68e957c147 Adds support for b-parasite's v2 BLE protocol (#3290) 2022-03-16 11:05:29 +13:00
andrewpc
99f5ed1461 Add support for QMP6988 Pressure sensor (#3192) 2022-03-15 08:09:17 +13:00
Rai-Rai
59f67796dc Fixed wrong comment (#3286) 2022-03-14 08:00:00 +13:00
stegm
aafdfa933e Add optimistic config flag to modbus select. (#3267) 2022-03-10 08:40:43 +13:00
Otto Winter
3208c8ed1e Bump docker dependencies (#3281) 2022-03-09 13:48:02 +01:00
dependabot[bot]
6bf733e24e Bump click from 8.0.3 to 8.0.4 (#3248)
Bumps [click](https://github.com/pallets/click) from 8.0.3 to 8.0.4.
- [Release notes](https://github.com/pallets/click/releases)
- [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/click/compare/8.0.3...8.0.4)

---
updated-dependencies:
- dependency-name: click
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 13:27:56 +01:00
dependabot[bot]
65d3e8fbfc Bump zeroconf from 0.38.3 to 0.38.4 (#3257)
Bumps [zeroconf](https://github.com/jstasiak/python-zeroconf) from 0.38.3 to 0.38.4.
- [Release notes](https://github.com/jstasiak/python-zeroconf/releases)
- [Commits](https://github.com/jstasiak/python-zeroconf/compare/0.38.3...0.38.4)

---
updated-dependencies:
- dependency-name: zeroconf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 13:27:33 +01:00
dependabot[bot]
a29d65d47c Bump pytest-asyncio from 0.18.1 to 0.18.2 (#3262)
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.18.1 to 0.18.2.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.18.1...v0.18.2)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 13:27:15 +01:00
Jesse Hills
efa8f0730d Merge pull request #3278 from esphome/bump-2022.3.0b1
2022.3.0b1
2022-03-09 20:57:07 +13:00
Jesse Hills
0af1edefff Bump version to 2022.4.0-dev 2022-03-09 20:07:50 +13:00
Jesse Hills
023d26f521 Bump version to 2022.3.0b1 2022-03-09 20:07:50 +13:00
Jesse Hills
5068619f1b Merge branch 'dev' into bump-2022.3.0b1 2022-03-09 20:07:49 +13:00
wilberforce
5b2457af0b Add visual step/min/max for webserver climate (#3275) 2022-03-09 10:28:16 +13:00
Jesse Hills
900b4f1af9 Bump esphome-dashboard to 20220309.0 (#3277) 2022-03-09 10:27:54 +13:00
JasperPlant
4c22a98b0b Add entity_category_diagnostics to SGP30 baseline sensors (#3272) 2022-03-08 15:21:13 +13:00
wilberforce
3b8ca80900 Webserver v2 (#2688)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-03-08 15:02:24 +13:00
Jesse Hills
b528f48417 Merge pull request #3201 from esphome/bump-2022.2.0b3
2022.2.0b3
2022-02-16 11:10:31 +13:00
Jesse Hills
ec7a79049a Bump version to 2022.2.0b3 2022-02-16 09:45:05 +13:00
Jesse Hills
6ddad6b299 Update HA addon token (#3200) 2022-02-16 09:45:05 +13:00
Maurice Makaay
16dc7762f9 Fix strlcpy() uses to make long SSIDs and passwords work (#3199)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2022-02-16 09:45:05 +13:00
Jesse Hills
dc0ed8857f Merge pull request #3198 from esphome/bump-2022.2.0b2
2022.2.0b2
2022-02-15 12:48:34 +13:00
Jesse Hills
bb6b77bd98 Bump version to 2022.2.0b2 2022-02-15 12:00:12 +13:00
Jesse Hills
dcc80f9032 Allow framework version validator to be maximum version (#3197) 2022-02-15 12:00:12 +13:00
Otto Winter
dd554bcdf4 Make generating combined binary output verbose (#3127) 2022-02-15 12:00:12 +13:00
Jesse Hills
f376a39e55 Clamp rotary_encoder restored value to min and max (#3184) 2022-02-15 12:00:12 +13:00
dependabot[bot]
8dcc9d6b66 Bump aioesphomeapi from 10.8.1 to 10.8.2 (#3182) 2022-02-15 12:00:11 +13:00
Jesse Hills
a576c9f21f Merge pull request #3180 from esphome/bump-2022.2.0b1
2022.2.0b1
2022-02-10 00:20:46 +13:00
Jesse Hills
71a438e2cb Bump version to 2022.2.0b1 2022-02-09 23:47:36 +13:00
Jesse Hills
272d6f2a8b Merge branch 'dev' into bump-2022.2.0b1 2022-02-09 23:47:36 +13:00
Jesse Hills
5dc776e55f Merge pull request #3068 from esphome/bump-2022.1.0b4
2022.1.0b4
2022-01-19 07:40:37 +13:00
Jesse Hills
72d60f30f7 Bump version to 2022.1.0b4 2022-01-18 15:49:31 +13:00
Oxan van Leeuwen
869743a742 Fail hard if no random bytes available for encryption (#3067) 2022-01-18 15:49:31 +13:00
Martin
7b03e07908 [modbus_controller] add missing skip_updates (#3063) 2022-01-18 15:49:31 +13:00
Paulus Schoutsen
348f880e15 bump dashboard to 20220116.0 (#3061) 2022-01-18 15:49:31 +13:00
Jesse Hills
ead597d0fb Merge pull request #3060 from esphome/bump-2022.1.0b3
2022.1.0b3
2022-01-17 13:13:40 +13:00
Jesse Hills
afbf989715 Bump version to 2022.1.0b3 2022-01-17 12:40:07 +13:00
Jesse Hills
01b62a16c3 Add number setting to web_server/rest_api (#3055) 2022-01-17 12:40:07 +13:00
Oxan van Leeuwen
c5eba04517 Remove deprecated attribute from virtual entity methods (#3056) 2022-01-17 12:40:07 +13:00
Oxan van Leeuwen
282313ab52 Rename post_build scripts to fix codeowners script (#3057) 2022-01-17 12:40:07 +13:00
Ohad Lutzky
d274545e77 Disable caching for binary download (#3054) 2022-01-17 12:40:07 +13:00
Jesse Hills
d3fda37615 Merge pull request #3042 from esphome/bump-2022.1.0b2
2022.1.0b2
2022-01-13 22:21:45 +13:00
Jesse Hills
cbe3092404 Bump version to 2022.1.0b2 2022-01-13 21:28:45 +13:00
Paulus Schoutsen
6dfe3039d0 Bump dashboard to 20220113.2 (#3041) 2022-01-13 21:28:45 +13:00
Paulus Schoutsen
d6009453df Add factory to download name (#3040) 2022-01-13 21:28:45 +13:00
Jesse Hills
c81323ef91 Merge pull request #3039 from esphome/bump-2022.1.0b1
2022.1.0b1
2022-01-13 12:12:07 +13:00
Jesse Hills
961c27f1c2 Bump version to 2022.1.0b1 2022-01-13 11:02:07 +13:00
Jesse Hills
fe4a14e6cc Merge branch 'dev' into bump-2022.1.0b1 2022-01-13 11:02:07 +13:00
Jesse Hills
50848c2f4d Merge pull request #2966 from esphome/bump-2021.12.3
2021.12.3
2021-12-30 14:54:49 +13:00
Jesse Hills
d32633b3c7 Update curl package version in docker (#2939) 2021-12-30 14:32:29 +13:00
Jesse Hills
b37739eec2 Bump version to 2021.12.3 2021-12-30 13:58:47 +13:00
Jesse Hills
28f87dc804 Remove -e for hassio images (#2964) 2021-12-30 13:58:47 +13:00
Jesse Hills
41879e41e6 Workaround installing as editable package not working (#2936) 2021-12-30 13:58:47 +13:00
Jesse Hills
fc0a6546a2 Only allow internal pins for dht sensor (#2940) 2021-12-30 13:58:47 +13:00
Jesse Hills
ffd4280d6c Require arduino in webserver for better validation (#2941) 2021-12-30 13:58:46 +13:00
Jesse Hills
db3b955b0f Merge pull request #2932 from esphome/bump-2021.12.2
2021.12.2
2021-12-21 12:45:27 +13:00
Jesse Hills
5516f65971 Bump version to 2021.12.2 2021-12-21 08:24:08 +13:00
Oxan van Leeuwen
9471df0a1b Fix MQTT button press action (#2917) 2021-12-21 08:24:07 +13:00
Oxan van Leeuwen
6d39f64be7 Don't disable idle task WDT when it's not enabled (#2856) 2021-12-21 08:24:07 +13:00
Jesse Hills
b89d0a9a73 Merge pull request #2915 from esphome/bump-2021.12.1
2021.12.1
2021-12-15 16:36:39 +13:00
Jesse Hills
4bb779d9a5 Bump version to 2021.12.1 2021-12-15 14:57:32 +13:00
wilberforce
386a5b6362 Allow button POST on press from web server (#2913) 2021-12-15 14:57:32 +13:00
Oxan van Leeuwen
e32a999cd0 Set text sensor state property to filter output (#2893) 2021-12-15 14:57:32 +13:00
Jesse Hills
bfbc6a4bad Merge pull request #2907 from esphome/bump-2021.12.0
2021.12.0
2021-12-12 07:59:37 +13:00
Jesse Hills
8c9e0e552d Bump version to 2021.12.0 2021-12-12 07:10:51 +13:00
Jesse Hills
8aaf9fd83f Merge pull request #2905 from esphome/bump-2021.12.0b6
2021.12.0b6
2021-12-11 21:54:49 +13:00
Jesse Hills
08057720b8 Bump version to 2021.12.0b6 2021-12-11 21:07:07 +13:00
Jesse Hills
bfaa648837 Bump esphome-dashboard to 20211211.0 (#2904) 2021-12-11 21:07:07 +13:00
Keith Burzinski
d504daef91 Fix for two points setting when fan_only_cooling is disabled (#2903)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Keith Burzinski <kburzinski@kbx-mbp2021.ad.kbx81.net>
2021-12-11 21:07:07 +13:00
Jesse Hills
b8d3ef2f49 Merge pull request #2899 from esphome/bump-2021.12.0b5
2021.12.0b5
2021-12-10 10:55:55 +13:00
Jesse Hills
3bf6320030 Bump version to 2021.12.0b5 2021-12-10 09:55:48 +13:00
Guillermo Ruffino
708b928c73 Modbus number/output use write single (#2896)
Co-authored-by: Martin <25747549+martgras@users.noreply.github.com>
2021-12-10 09:55:48 +13:00
Jesse Hills
649366ff44 Fix published state for modbus number (#2894) 2021-12-10 09:55:47 +13:00
Jesse Hills
e5c9e87fad Merge pull request #2890 from esphome/bump-2021.12.0b4
2021.12.0b4
2021-12-10 09:50:29 +13:00
Jesse Hills
f3d9d707b6 Bump version to 2021.12.0b4 2021-12-08 12:58:14 +13:00
Jesse Hills
090e10730c Bump esphome-dashboard to 20211208.0 (#2887) 2021-12-08 12:58:14 +13:00
Jesse Hills
fbc84861c7 Use new platform component config blocks for wizard (#2885) 2021-12-08 12:58:14 +13:00
Carlos Garcia Saura
e763469af8 Feed watchdog while setting up OTA (#2876) 2021-12-08 12:58:14 +13:00
Jesse Hills
3c0c514e44 Merge pull request #2880 from esphome/bump-2021.12.0b3
2021.12.0b3
2021-12-07 15:27:08 +13:00
Jesse Hills
ed5e2dd332 Bump version to 2021.12.0b3 2021-12-07 07:47:48 +13:00
Jesse Hills
09b7c6f550 Bump esphome-dashboard to 20211207.0 (#2877) 2021-12-07 07:47:48 +13:00
Oxan van Leeuwen
df315a1f51 Feed watchdog when no component loops (#2857) 2021-12-07 07:47:48 +13:00
Jesse Hills
7ee4bb621c Allow wizard to specify secrets (#2875) 2021-12-07 07:47:48 +13:00
Jesse Hills
24874f4c3c Adopt using wifi secrets that should exist at this point (#2874) 2021-12-07 07:47:48 +13:00
Jesse Hills
c128880033 Add endpoint to fetch secrets keys (#2873) 2021-12-07 07:47:48 +13:00
Massimiliano Ravelli
a66e94a0b0 Ignore already stopped dhcp for ethernet (#2862)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-07 07:47:48 +13:00
Oxan van Leeuwen
56870ed4a8 Fix MCP23x17 not disabling pullup after config change (#2855) 2021-12-07 07:47:48 +13:00
Martin
3ac720df47 SPS30 : fix i2c read size (#2866) 2021-12-07 07:47:48 +13:00
Carlos Garcia Saura
1bc757ad06 ADC: Turn verbose the debugging "got voltage" (#2863) 2021-12-07 07:47:48 +13:00
Martin
f72abc6f3d tlc59208f : fix compilation error (#2867) 2021-12-07 07:47:48 +13:00
Jesse Hills
5ac88de985 Bump esphome-dashboard to 20211206.0 (#2870) 2021-12-07 07:47:48 +13:00
Jesse Hills
0826b367d6 Merge pull request #2853 from esphome/bump-2021.12.0b2
2021.12.0b2
2021-12-03 08:07:30 +13:00
Jesse Hills
329bf861d6 Bump version to 2021.12.0b2 2021-12-03 07:54:34 +13:00
Oxan van Leeuwen
9dcd3d18a0 Update ota_component.cpp (#2852) 2021-12-03 07:54:34 +13:00
Jesse Hills
db66cd88b6 Merge pull request #2851 from esphome/bump-2021.12.0b1
2021.12.0b1
2021-12-02 21:32:43 +13:00
Jesse Hills
86c205fe43 Remove blank line 2021-12-02 21:08:11 +13:00
Jesse Hills
c6414138c7 Bump version to 2021.12.0b1 2021-12-02 19:38:49 +13:00
Jesse Hills
36b355eb82 Merge branch 'dev' into bump-2021.12.0b1 2021-12-02 19:38:44 +13:00
Jesse Hills
7be9291b13 Merge pull request #2821 from esphome/bump-2021.11.4
2021.11.4
2021-11-29 13:23:47 +13:00
Jesse Hills
ea9e75039b Bump version to 2021.11.4 2021-11-29 10:18:49 +13:00
Conclusio
a5fb036011 Add delay to improve stability (#2793) 2021-11-29 10:18:48 +13:00
Dave T
e55506f9db Correct bitmask for third color (blue) scaling. (#2817) 2021-11-29 10:18:48 +13:00
Carlos Garcia Saura
50ec1d0445 Fix compilation error for WPA enterprise in ESP-IDF (#2815) 2021-11-29 10:18:48 +13:00
Oxan van Leeuwen
3d5e1d8d91 Fix parsing of multiple values in EZO sensor (#2814)
Co-authored-by: Lydia Sevelt <LydiaSevelt@gmail.com>
2021-11-29 10:18:48 +13:00
Oxan van Leeuwen
db2128a344 Fix parsing numbers in Anova (#2816) 2021-11-29 10:18:48 +13:00
Jesse Hills
21db43db06 Merge pull request #2808 from esphome/bump-2021.11.3
2021.11.3
2021-11-28 00:01:16 +13:00
Jesse Hills
5009b3029f Bump version to 2021.11.3 2021-11-27 21:13:01 +13:00
Maurice Makaay
57a029189c Add missing nvs_flash_init() to ESP32 preferences code (#2805)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-11-27 21:13:01 +13:00
Jesse Hills
0cb715bb76 Merge pull request #2799 from esphome/bump-2021.11.2
2021.11.2
2021-11-26 09:25:37 +13:00
Jesse Hills
7d03823afd Bump version to 2021.11.2 2021-11-26 09:02:54 +13:00
Oxan van Leeuwen
8e1c9f5042 Fix parsing numbers from null-terminated buffers (#2755) 2021-11-26 09:02:54 +13:00
Samuel Sieb
980b7cda8f Remove floating point ops from the ISR (#2751)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2021-11-26 09:02:53 +13:00
Kamil Trzciński
3a72dd5cb6 esp32_camera_web_server: Improve support for MotionEye (#2777) 2021-11-26 09:02:53 +13:00
Dave T
3178243811 Fix frame scaling for animated gifs (#2750) 2021-11-26 09:02:53 +13:00
Maurice Makaay
d30e2f2a4f Allow UART debug configuration with no after: definition (#2753) 2021-11-26 09:02:53 +13:00
Jesse Hills
6226dae05c Merge pull request #2744 from esphome/bump-2021.11.1
2021.11.1
2021-11-17 23:45:43 +13:00
Jesse Hills
9c6a475a6e Bump version to 2021.11.1 2021-11-17 23:31:38 +13:00
Franck Nijhof
8294d10d5b Re-instate device class update for binary sensors (#2743) 2021-11-17 23:31:38 +13:00
Evgeny
67558bec47 Fix HM3301 AQI index calculator (#2739) 2021-11-17 23:31:38 +13:00
Jesse Hills
84873d4074 Merge pull request #2742 from esphome/bump-2021.11.0
2021.11.0
2021-11-17 22:18:29 +13:00
Jesse Hills
58a0b28a39 Bump version to 2021.11.0 2021-11-17 21:58:30 +13:00
Jesse Hills
b37d3a66cc Merge pull request #2738 from esphome/bump-2021.11.0b9
2021.11.0b9
2021-11-17 10:27:41 +13:00
Jesse Hills
7e495a5e27 Bump version to 2021.11.0b9 2021-11-17 08:00:26 +13:00
rotarykite
c41547fd4a Fix senseair component uart read timeout (#2658)
Co-authored-by: DAVe3283 <DAVe3283+GitHub@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Chua Jun Chieh <junchieh.chua@softspace.com.my>
2021-11-17 08:00:26 +13:00
Ryan Hoffman
0d47d41c85 Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737)
#1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
2021-11-17 08:00:26 +13:00
Jesse Hills
41a3a17456 Merge pull request #2734 from esphome/bump-2021.11.0b8
2021.11.0b8
2021-11-16 13:50:10 +13:00
Jesse Hills
cbbafbcca2 Bump version to 2021.11.0b8 2021-11-16 12:53:56 +13:00
Jesse Hills
c75566b374 Fix zeroconf time comparisons (#2733)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-11-16 12:53:56 +13:00
Jesse Hills
7279f1fcc1 Merge pull request #2732 from esphome/bump-2021.11.0b7
2021.11.0b7
2021-11-16 12:19:07 +13:00
Jesse Hills
d7432f7c10 Bump version to 2021.11.0b7 2021-11-16 11:05:51 +13:00
Jesse Hills
b0a0a153f3 Improv serial/checksum changes (#2731)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-16 11:05:51 +13:00
Jesse Hills
024632dbd0 Merge pull request #2730 from esphome/bump-2021.11.0b6
2021.11.0b6
2021-11-16 10:53:11 +13:00
Jesse Hills
0a545a28b9 Bump version to 2021.11.0b6 2021-11-16 09:59:00 +13:00
Jesse Hills
0f2df59998 Add zeroconf as a direct dependency and lock the version (#2729) 2021-11-16 09:58:59 +13:00
Jesse Hills
29a7d32f77 Merge pull request #2725 from esphome/bump-2021.11.0b5
2021.11.0b5
2021-11-15 13:42:59 +13:00
Jesse Hills
687a7e9b2f Bump version to 2021.11.0b5 2021-11-15 12:02:18 +13:00
Alexandre-Jacques St-Jacques
09e8782318 Remove unnecessary duplicate touch_pad_filter_start (#2724) 2021-11-15 12:02:18 +13:00
Jesse Hills
f2aea02210 Merge pull request #2723 from esphome/bump-2021.11.0b4
2021.11.0b4
2021-11-15 11:42:59 +13:00
Jesse Hills
194f922312 Bump version to 2021.11.0b4 2021-11-15 11:03:40 +13:00
Jesse Hills
fea3c48098 Fix indentation of write_lambda for modbus_controller number (#2722) 2021-11-15 11:03:39 +13:00
Sergey V. DUDANOV
c2f57baec2 RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
f4a140e126 Feed WDT between doing ESP32 touchpad measurements (#2720) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
ab506b09fe Restore InterruptLock on wifi-less ESP8266 (#2712) 2021-11-15 11:03:39 +13:00
Krzysztof Białek
87e1cdeedb Allow setting custom command_topic for Select and Number components (#2714) 2021-11-15 11:03:39 +13:00
Jesse Hills
81a36146ef Bump ESPAsyncWebServer to 2.1.0 (#2686) 2021-11-15 11:03:39 +13:00
Jesse Hills
7fa4a68a27 Merge pull request #2704 from esphome/bump-2021.11.0b3
2021.11.0b3
2021-11-12 17:21:58 +13:00
Jesse Hills
f1c5e2ef81 Bump version to 2021.11.0b3 2021-11-12 16:12:31 +13:00
lcavalli
b526155cce Update device classes for binary sensors (#2703) 2021-11-12 16:12:31 +13:00
Jesse Hills
62c3f301e7 Only allow prometheus when using arduino (#2697) 2021-11-12 16:12:31 +13:00
Jesse Hills
38cb988809 Remove my.ha links from improv (#2695) 2021-11-12 16:12:31 +13:00
Jesse Hills
b976ac54c8 Merge pull request #2693 from esphome/bump-2021.11.0b2
2021.11.0b2
2021-11-11 12:52:35 +13:00
Jesse Hills
78026e766f Bump version to 2021.11.0b2 2021-11-11 12:25:41 +13:00
Oxan van Leeuwen
b4cd8d21a5 Enable addressable light power supply based on raw values (#2690) 2021-11-11 12:25:41 +13:00
Maurice Makaay
7552893311 Uart debugging support (#2478)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 12:25:41 +13:00
Carlos Garcia Saura
21c896d8f8 [remote_transmitter] accurate pulse timing for ESP8266 (#2476) 2021-11-11 12:25:40 +13:00
Jesse Hills
4b7fe202ec Fix template number initial value being NaN (#2692) 2021-11-11 12:25:40 +13:00
Jesse Hills
9f4519210f Merge pull request #2691 from esphome/bump-2021.11.0b1
2021.11.0b1
2021-11-11 11:05:45 +13:00
Jesse Hills
b0506afa5b Merge branch 'beta' into bump-2021.11.0b1 2021-11-11 10:48:23 +13:00
Jesse Hills
8cbb379898 Remove import (not sure how it got there) 2021-11-11 10:35:18 +13:00
Jesse Hills
b226215593 Bump version to 2021.11.0b1 2021-11-11 10:10:05 +13:00
Jesse Hills
19970729a9 Merge branch 'dev' into bump-2021.11.0b1 2021-11-11 10:10:04 +13:00
Jesse Hills
d2ebfd2833 Merge pull request #2634 from esphome/bump-2021.10.3
2021.10.3
2021-10-27 11:21:08 +13:00
Jesse Hills
bd782fc828 Bump version to 2021.10.3 2021-10-27 10:49:11 +13:00
Jesse Hills
23560e608c Fix select.set using lambda (#2633) 2021-10-27 10:49:10 +13:00
Jan Čermák
f1377b560e Fix pin number validation for sn74hc595 (#2621) 2021-10-27 10:49:10 +13:00
Martin
72108684ea fix modbus output (#2630) 2021-10-27 10:49:10 +13:00
Jesse Hills
c6adaaea97 Remove power and energy from sensors that are not true power (#2628) 2021-10-27 10:49:10 +13:00
Otto Winter
91999a38ca Fix glue code missing micros() (#2623) 2021-10-27 10:49:10 +13:00
0hax
b34eed125d Teleinfo ptec (#2599)
* teleinfo: handle historical mode correctly.

In historical mode, tags like PTEC leads to an issue where we detect a
timestamp wheras this is not possible in historical mode.

PTEC teleinfo tag looks like:
    PTEC HP..
Instead of the usual format
    IINST1 001 I

This make our data parsing fails.

While at here, make sure we continue parsing other tags even if parsing
one of the tag fails.

Signed-off-by: 0hax <0hax@protonmail.com>

* teleinfo: fix compilation with loglevel set to debug.

Signed-off-by: 0hax <0hax@protonmail.com>
2021-10-27 10:49:10 +13:00
Stefan Agner
2abe09529a Autodetect flash size (#2615) 2021-10-27 10:49:10 +13:00
Otto Winter
9aaaf4dd4b Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) 2021-10-27 10:49:09 +13:00
Andreas Hergert
cbfbcf7f1b fixed dependency for pca9685 component (#2614)
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Andreas <andreas.hergert@otrs.com>
2021-10-27 10:49:09 +13:00
Otto Winter
c7651dc40d Merge pull request #2613 from esphome/bump-2021.10.2
2021.10.2
2021-10-22 18:35:28 +02:00
Otto winter
eda1c471ad Bump version to 2021.10.2 2021-10-22 18:26:24 +02:00
Andreas Hergert
c7ef18fbc4 Bugfix tca9548a and idf refactor anh (#2612)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
2021-10-22 18:26:23 +02:00
Otto Winter
901ec918b1 Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) 2021-10-22 18:26:23 +02:00
Otto Winter
6bdae55ee1 Fix compiler warnings and update platformio line filter (#2607) 2021-10-22 18:26:23 +02:00
Otto Winter
dfb96e4b7f Add owner to all libraries used (#2604) 2021-10-22 18:26:22 +02:00
Otto Winter
ff2c316b18 Re-raise keyboardinterrupt (#2603) 2021-10-22 18:26:22 +02:00
Otto Winter
5be52f71f9 Add OTA upload compression for ESP8266 (#2601) 2021-10-22 18:26:22 +02:00
Otto Winter
42873dd37c Bump noise-c from 0.1.3 to 0.1.4 (#2602) 2021-10-22 18:26:21 +02:00
Otto Winter
f93e7d4e3a Fix socket connection closed not detected (#2587) 2021-10-22 18:26:21 +02:00
Oxan van Leeuwen
bbcd523967 Fix validation of addressable light IDs (#2588) 2021-10-22 18:26:21 +02:00
dependabot[bot]
68cbe58d00 Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-22 18:26:12 +02:00
Oxan van Leeuwen
115bca98f1 Fix old-style arduino_version on ESP8266 and with magic values (#2591) 2021-10-22 18:24:48 +02:00
Oxan van Leeuwen
ed0b34b2fe Fix pin/component switchup in SX1509 pin configuration (#2593) 2021-10-22 18:24:48 +02:00
Oxan van Leeuwen
ab34401421 Fix PlatformIO version for latest Arduino framework (#2590) 2021-10-22 18:24:48 +02:00
Otto Winter
eed0c18d65 Fix HeatpumpIR pin (#2585) 2021-10-22 18:24:47 +02:00
Otto Winter
e5a38ce748 Merge pull request #2580 from esphome/bump-2021.10.1
2021.10.1
2021-10-21 15:31:26 +02:00
Otto Winter
7d9d9fcf36 Fix platformio_install_deps no longer installing all lib_deps (#2584) 2021-10-21 15:29:53 +02:00
Otto Winter
f0aba6ceb2 Fix platformio version in Dockerfile doesn't match requirements (#2582) 2021-10-21 14:53:22 +02:00
Otto Winter
ab07ee57c6 Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) 2021-10-21 14:39:55 +02:00
Otto winter
eae3d72a4d Bump version to 2021.10.1 2021-10-21 14:23:08 +02:00
Otto Winter
7b8d826704 Fix ESP8266 OTA adds unnecessary Update library (#2579) 2021-10-21 14:23:07 +02:00
Otto Winter
e7baa42e63 Arduino global delay/millis/... symbols workaround (#2575) 2021-10-21 14:23:07 +02:00
Otto Winter
2f32833a22 Fix wifi ble coexistence check (#2573) 2021-10-21 14:23:07 +02:00
Otto Winter
f6935a4b4b Fix ESP8266 GPIO0 Pullup Validation (#2572) 2021-10-21 14:23:06 +02:00
Maurice Makaay
332c9e891b Fix MDNS for ESP8266 devices (#2571)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Otto winter <otto@otto-winter.com>
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-10-21 14:23:06 +02:00
Jesse Hills
b91ee4847f Merge pull request #2570 from esphome/bump-2021.10.0
2021.10.0
2021-10-21 08:37:26 +13:00
Jesse Hills
625463d871 Bump version to 2021.10.0 2021-10-21 07:57:10 +13:00
Jesse Hills
6433a01e07 Merge pull request #2567 from esphome/bump-2021.10.0b11
2021.10.0b11
2021-10-21 07:47:22 +13:00
Jesse Hills
56cc31e8e7 Bump version to 2021.10.0b11 2021-10-21 07:32:27 +13:00
Otto Winter
3af297aa76 Revert nextion clang-tidy changes (#2566) 2021-10-21 07:32:27 +13:00
Otto Winter
996ec59d28 Move running process log line to debug level (#2565) 2021-10-21 07:32:26 +13:00
Jesse Hills
95593eeeab Bump esphome-dashboard to 20211021.0 (#2564) 2021-10-21 07:32:20 +13:00
Jesse Hills
dad244fb7a A few esp32_ble_server/improv fixes (#2562) 2021-10-21 07:31:55 +13:00
Jesse Hills
adb5d27d95 Merge pull request #2561 from esphome/bump-2021.10.0b10
2021.10.0b10
2021-10-20 17:28:37 +13:00
Jesse Hills
8456a8cecb Bump version to 2021.10.0b10 2021-10-20 16:39:24 +13:00
Jesse Hills
b9f66373c1 Bump esphome-dashboard to 20211020.1 (#2559) 2021-10-20 16:39:16 +13:00
Jesse Hills
9ac365feef Fix HA addon so it does not have logout button (#2558) 2021-10-20 16:38:46 +13:00
Jesse Hills
43bbd58a44 Merge pull request #2557 from esphome/bump-2021.10.0b9
2021.10.0b9
2021-10-20 11:18:40 +13:00
Jesse Hills
7feffa64f3 Bump version to 2021.10.0b9 2021-10-20 11:00:02 +13:00
Jesse Hills
ea0977abb4 Bump dashboard to 20211020.0 (#2556) 2021-10-20 10:59:53 +13:00
Jesse Hills
4c83dc7c28 Merge pull request #2555 from esphome/bump-2021.10.0b8
2021.10.0b8
2021-10-20 10:30:59 +13:00
Jesse Hills
e10ab1da78 Bump version to 2021.10.0b8 2021-10-20 10:14:30 +13:00
Martin
1b0e60374b ignore exception when not waiting for a response (#2552) 2021-10-20 10:14:30 +13:00
Oxan van Leeuwen
3a760fbb44 Fix ADC pin validation on ESP32-C3 (#2551) 2021-10-20 10:14:30 +13:00
Jesse Hills
6ef57a2973 Merge pull request #2550 from esphome/bump-2021.10.0b7
2021.10.0b7
2021-10-19 16:22:29 +13:00
Jesse Hills
3e9c7f2e9f Bump version to 2021.10.0b7 2021-10-19 15:47:31 +13:00
Jesse Hills
430598b7a1 Bump dashboard to 20211019.0 (#2549) 2021-10-19 15:47:26 +13:00
Jesse Hills
91611b09b4 Merge pull request #2545 from esphome/bump-2021.10.0b6
2021.10.0b6
2021-10-18 21:41:22 +13:00
Jesse Hills
ecd115851f Bump version to 2021.10.0b6 2021-10-18 21:26:36 +13:00
Maurice Makaay
4a1e50fed1 OTA firmware MD5 check + password support for esp-idf (#2507)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-10-18 21:26:36 +13:00
Jesse Hills
d6d037047b Merge pull request #2544 from esphome/bump-2021.10.0b5
2021.10.0b5
2021-10-18 16:37:43 +13:00
Jesse Hills
b5734c2b20 Bump version to 2021.10.0b5 2021-10-18 15:31:01 +13:00
Oxan van Leeuwen
723fb7eaac Autodetect ESP32 variant (#2530)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-10-18 15:31:01 +13:00
Jesse Hills
63a9acaa19 Fix Bluetooth setup_priorities (#2458)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-18 15:31:00 +13:00
Jesse Hills
0524f8c677 Fix const used for IDF recommended version (#2542) 2021-10-18 15:31:00 +13:00
Otto Winter
70b62f272e Only show timestamp for dashboard access logs (#2540) 2021-10-18 15:31:00 +13:00
Jesse Hills
f0089b7940 Merge pull request #2539 from esphome/bump-2021.10.0b4
2021.10.0b4
2021-10-17 21:17:54 +13:00
Jesse Hills
4b44280d53 Bump version to 2021.10.0b4 2021-10-17 20:34:07 +13:00
Paulus Schoutsen
f045382d20 Bump dashboard to 20211015.0 (#2525)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-10-17 20:34:03 +13:00
Jesse Hills
db3fa1ade7 Allow downloading all bin files from backend in dashboard (#2514)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-17 20:28:43 +13:00
Oxan van Leeuwen
f83950fd75 Fix bitshift on read in ADE7953 (#2537) 2021-10-17 20:28:43 +13:00
Oxan van Leeuwen
4dd1bf920d Replace framework version_hint with source option (#2529) 2021-10-17 20:28:43 +13:00
Martin
98755f3621 Fix bug in register name definition (#2526) 2021-10-17 20:28:43 +13:00
Keith Burzinski
c3a8a044b9 Fix Nextion HTTPClient error for ESP32 (#2524) 2021-10-17 20:28:42 +13:00
Martin
15b5ea43a7 Add pressure compensation during runtime (#2493)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-17 20:28:42 +13:00
Jesse Hills
ec683fc227 Merge pull request #2523 from esphome/bump-2021.10.0b3
2021.10.0b3
2021-10-15 10:15:26 +13:00
Jesse Hills
d4e65eb82a Bump version to 2021.10.0b3 2021-10-15 09:42:44 +13:00
Paul Monigatti
10c6601b0a Revert "Added test for bme680_bsec" (#2518)
This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions.
2021-10-15 09:42:44 +13:00
Oxan van Leeuwen
73940bc1bd Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) 2021-10-15 09:42:43 +13:00
Paul Monigatti
9b7fb829f9 Fix: Color modes not being correctly used in light partitions (#2513) 2021-10-15 09:42:43 +13:00
Dmitriy Lopatko
c51d8c9021 add missing include in sgp30 (#2517) 2021-10-15 09:42:43 +13:00
Paul Monigatti
d8a6dfe5ce Fix BME680_BSEC compilation issue with ESP32 (#2516) 2021-10-15 09:42:43 +13:00
Oxan van Leeuwen
5f7cef0b06 Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) 2021-10-15 09:42:43 +13:00
Paul Monigatti
48ff2ffc68 Fix: Light flash not restoring previous LightState (#2383)
* Update light state when transformer has finished

* Revert writing direct to output

* Correct handling of zero-length light transformers

* Allow transformers to handle zero-length transitions, and check more boundary conditions when transitioning back to start state

* Removed log.h

* Fixed race condition between LightFlashTransformer.apply() and is_finished()

* clang-format

* Step progress from 0.0f to 1.0f at t=start_time for zero-length transforms to avoid divide-by-zero
2021-10-15 09:42:43 +13:00
Oxan van Leeuwen
b3b9ccd314 Fix light state remaining on after turn off with transition (#2509) 2021-10-15 09:42:43 +13:00
Jesse Hills
e63c7b483b Merge pull request #2505 from esphome/bump-2021.10.0b2
2021.10.0b2
2021-10-13 22:45:29 +13:00
Jesse Hills
f57980b069 Bump version to 2021.10.0b2 2021-10-13 22:30:29 +13:00
Jesse Hills
7006aa0d2a Merge branch 'dev' into beta 2021-10-13 22:11:53 +13:00
Jesse Hills
8051c1ca99 Merge pull request #2504 from esphome/bump-2021.10.0b1
2021.10.0b1
2021-10-13 21:38:41 +13:00
Jesse Hills
a779592414 Bump version to 2021.10.0b1 2021-10-13 16:40:46 +13:00
Jesse Hills
112215848d Merge branch 'dev' into bump-2021.10.0b1 2021-10-13 16:40:46 +13:00
186 changed files with 6224 additions and 1833 deletions

View File

@@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/ambv/black
rev: 22.1.0
rev: 22.3.0
hooks:
- id: black
args:
@@ -26,7 +26,7 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
rev: v2.31.1
hooks:
- id: pyupgrade
args: [--py38-plus]

View File

@@ -82,6 +82,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/honeywellabp/* @RubyBailey
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/i2c/* @esphome/core
esphome/components/improv_serial/* @esphome/core
esphome/components/ina260/* @MrEditor97
@@ -151,6 +152,7 @@ esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @cstaahl @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje
esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3
@@ -168,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core
esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny
@@ -175,6 +178,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/spi/* @esphome/core
esphome/components/ssd1322_base/* @kbx81
esphome/components/ssd1322_spi/* @kbx81
@@ -222,4 +226,5 @@ esphome/components/whirlpool/* @glmnet
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xpt2046/* @numo68

View File

@@ -6,13 +6,13 @@
ARG BASEIMGTYPE=docker
# https://github.com/hassio-addons/addon-debian-base/releases
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
FROM debian:bullseye-20220125-slim AS base-docker-amd64
FROM debian:bullseye-20220125-slim AS base-docker-arm64
FROM debian:bullseye-20220125-slim AS base-docker-armv7
FROM debian:bullseye-20220328-slim AS base-docker-amd64
FROM debian:bullseye-20220328-slim AS base-docker-arm64
FROM debian:bullseye-20220328-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
@@ -23,7 +23,7 @@ RUN \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
python3=3.9.2-3 \
python3-pip=20.3.4-4 \
python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \

View File

@@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
return var
def validate_wait_until(value):
schema = cv.Schema(
{
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_TIMEOUT): cv.templatable(
cv.positive_time_period_milliseconds
),
}
)
if isinstance(value, dict) and CONF_CONDITION in value:
return schema(value)
return validate_wait_until({CONF_CONDITION: value})
_validate_wait_until = cv.maybe_simple_value(
{
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds),
},
key=CONF_CONDITION,
)
@register_action("wait_until", WaitUntilAction, validate_wait_until)
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
async def wait_until_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)

View File

@@ -76,6 +76,8 @@ async def to_code(config):
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])

View File

@@ -23,7 +23,7 @@ static const char *const TAG = "api.connection";
static const int ESP32_CAMERA_STOP_STREAM = 5000;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT)

View File

@@ -7,7 +7,6 @@
#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 "user_services.h"

View File

@@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s
#endif
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response();
return this->client_->send_list_entities_services_response(resp);

View File

@@ -1,8 +1,8 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/defines.h"
#include "util.h"
namespace esphome {
namespace api {
@@ -11,7 +11,7 @@ class APIConnection;
class ListEntitiesIterator : public ComponentIterator {
public:
ListEntitiesIterator(APIServer *server, APIConnection *client);
ListEntitiesIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
@@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator {
} // namespace api
} // namespace esphome
#include "api_server.h"

View File

@@ -1,5 +1,4 @@
#include "proto.h"
#include "util.h"
#include "esphome/core/log.h"
namespace esphome {

View File

@@ -195,6 +195,20 @@ class ProtoWriteBuffer {
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
}
void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
this->write((value >> 32) & 0xFF);
this->write((value >> 40) & 0xFF);
this->write((value >> 48) & 0xFF);
this->write((value >> 56) & 0xFF);
}
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
@@ -229,6 +243,15 @@ class ProtoWriteBuffer {
}
this->encode_uint32(field_id, uvalue, force);
}
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
uint64_t uvalue;
if (value < 0) {
uvalue = ~(value << 1);
} else {
uvalue = value << 1;
}
this->encode_uint64(field_id, uvalue, force);
}
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
this->encode_field_raw(field_id, 2);
size_t begin = this->buffer_->size();

View File

@@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) {
#ifdef USE_LOCK
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api
} // namespace esphome

View File

@@ -1,9 +1,9 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/controller.h"
#include "esphome/core/defines.h"
#include "util.h"
namespace esphome {
namespace api {
@@ -12,7 +12,7 @@ class APIConnection;
class InitialStateIterator : public ComponentIterator {
public:
InitialStateIterator(APIServer *server, APIConnection *client);
InitialStateIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
@@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator {
} // namespace api
} // namespace esphome
#include "api_server.h"

View File

@@ -38,7 +38,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
const auto &data = service_data.data;
const uint8_t protocol_version = data[0] >> 4;
if (protocol_version != 1) {
if (protocol_version != 1 && protocol_version != 2) {
ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
return false;
}
@@ -57,9 +57,15 @@ 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 * Celsius.
uint16_t temp_millicelcius = data[4] << 8 | data[5];
float temp_celcius = temp_millicelcius / 1000.0f;
// Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2).
float temp_celsius;
if (protocol_version == 1) {
uint16_t temp_millicelsius = data[4] << 8 | data[5];
temp_celsius = temp_millicelsius / 1000.0f;
} else {
int16_t temp_centicelsius = data[4] << 8 | data[5];
temp_celsius = temp_centicelsius / 100.0f;
}
// Relative air humidity in the range [0, 2^16).
uint16_t humidity = data[6] << 8 | data[7];
@@ -76,7 +82,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
battery_voltage_->publish_state(battery_voltage);
}
if (temperature_ != nullptr) {
temperature_->publish_state(temp_celcius);
temperature_->publish_state(temp_celsius);
}
if (humidity_ != nullptr) {
humidity_->publish_state(humidity_percent);

View File

@@ -118,16 +118,21 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
this->set_states_(espbt::ClientState::IDLE);
break;
}
this->conn_id = param->open.conn_id;
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id);
break;
}
case ESP_GATTC_CONNECT_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
this->conn_id = param->connect.conn_id;
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret);
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
}
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
param->cfg_mtu.status);
this->set_states_(espbt::ClientState::IDLE);
break;
}
@@ -139,7 +144,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
return;
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear();
@@ -201,6 +206,32 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
}
}
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
// This event is sent by the server when it requests security
case ESP_GAP_BLE_SEC_REQ_EVT:
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
// This event is sent once authentication has completed
case ESP_GAP_BLE_AUTH_CMPL_EVT:
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
param->ble_security.auth_cmpl.auth_mode);
}
break;
// There are other events we'll want to implement at some point to support things like pass key
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
default:
break;
}
}
// Parse GATT values into a float for a sensor.
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {

View File

@@ -11,6 +11,7 @@
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_bt_defs.h>
#include <esp_gatt_common_api.h>
namespace esphome {
namespace ble_client {
@@ -86,6 +87,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void connect() override;

View File

@@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True
CONF_CAN_ID = "can_id"
CONF_CAN_ID_MASK = "can_id_mask"
CONF_USE_EXTENDED_ID = "use_extended_id"
CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request"
CONF_CANBUS_ID = "canbus_id"
CONF_BIT_RATE = "bit_rate"
CONF_ON_FRAME = "on_frame"
@@ -122,6 +123,7 @@ async def register_canbus(var, config):
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean,
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
},
validate_id,
@@ -140,6 +142,11 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
)
cg.add(var.set_use_extended_id(use_extended_id))
remote_transmission_request = await cg.templatable(
config[CONF_REMOTE_TRANSMISSION_REQUEST], args, bool
)
cg.add(var.set_remote_transmission_request(remote_transmission_request))
data = config[CONF_DATA]
if isinstance(data, bytes):
data = [int(x) for x in data]

View File

@@ -22,20 +22,22 @@ void Canbus::dump_config() {
}
}
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
const std::vector<uint8_t> &data) {
struct CanFrame can_message;
uint8_t size = static_cast<uint8_t>(data.size());
if (use_extended_id) {
ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} else {
ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
}
if (size > CAN_MAX_DATA_LENGTH)
size = CAN_MAX_DATA_LENGTH;
can_message.can_data_length_code = size;
can_message.can_id = can_id;
can_message.use_extended_id = use_extended_id;
can_message.remote_transmission_request = remote_transmission_request;
for (int i = 0; i < size; i++) {
can_message.data[i] = data[i];

View File

@@ -62,7 +62,12 @@ class Canbus : public Component {
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void loop() override;
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
const std::vector<uint8_t> &data);
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
// for backwards compatibility only
this->send_data(can_id, use_extended_id, false, data);
}
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
@@ -96,21 +101,26 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
void set_remote_transmission_request(bool remote_transmission_request) {
this->remote_transmission_request_ = remote_transmission_request;
}
void play(Ts... x) override {
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
auto use_extended_id =
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
if (this->static_) {
this->parent_->send_data(can_id, use_extended_id, this->data_static_);
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
} else {
auto val = this->data_func_(x...);
this->parent_->send_data(can_id, use_extended_id, val);
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
}
}
protected:
optional<uint32_t> can_id_{};
optional<bool> use_extended_id_{};
bool remote_transmission_request_{false};
bool static_{false};
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};

View File

@@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t {
CLIMATE_SWING_HORIZONTAL = 3,
};
/// Enum for all modes a climate swing can be in
/// Enum for all preset modes
enum ClimatePreset : uint8_t {
/// No preset is active
CLIMATE_PRESET_NONE = 0,
@@ -108,7 +108,7 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
/// Convert the given PresetMode to a human-readable string.
const LogString *climate_preset_to_string(ClimatePreset preset);
} // namespace climate

View File

@@ -141,7 +141,7 @@ class ClimateTraits {
}
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
std::set<ClimateSwingMode> get_supported_swing_modes() const { return supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }

View File

@@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) {
void IRAM_ATTR ESPOneWire::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->last_family_discrepancy_ = 0;
this->rom_number_ = 0;
}
uint64_t IRAM_ATTR ESPOneWire::search() {
@@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() {
if (!branch) {
last_zero = id_bit_number;
if (last_zero < 9) {
this->last_discrepancy_ = last_zero;
}
}
}

View File

@@ -60,7 +60,6 @@ class ESPOneWire {
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
uint8_t last_family_discrepancy_{0};
bool last_device_flag_{false};
uint64_t rom_number_{0};
};

View File

@@ -1,13 +1,18 @@
import esphome.codegen as cg
from esphome.components import time
import esphome.config_validation as cv
from esphome import pins, automation
from esphome.const import (
CONF_HOUR,
CONF_ID,
CONF_MINUTE,
CONF_MODE,
CONF_NUMBER,
CONF_PINS,
CONF_RUN_DURATION,
CONF_SECOND,
CONF_SLEEP_DURATION,
CONF_TIME_ID,
CONF_WAKEUP_PIN,
)
@@ -15,6 +20,7 @@ from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
)
WAKEUP_PINS = {
@@ -39,6 +45,30 @@ WAKEUP_PINS = {
39,
],
VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
VARIANT_ESP32S2: [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
],
}
@@ -87,6 +117,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup"
CONF_DEFAULT = "default"
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
CONF_UNTIL = "until"
WAKEUP_CAUSES_SCHEMA = cv.Schema(
{
@@ -177,13 +208,19 @@ async def to_code(config):
cg.add_define("USE_DEEP_SLEEP")
DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(DeepSleepComponent),
cv.Optional(CONF_SLEEP_DURATION): cv.templatable(
cv.positive_time_period_milliseconds
),
}
DEEP_SLEEP_ENTER_SCHEMA = cv.All(
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(DeepSleepComponent),
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
cv.positive_time_period_milliseconds
),
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day),
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
),
cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID),
)
@@ -203,6 +240,14 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args):
if CONF_SLEEP_DURATION in config:
template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32)
cg.add(var.set_sleep_duration(template_))
if CONF_UNTIL in config:
until = config[CONF_UNTIL]
cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND]))
time_ = await cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time(time_))
return var

View File

@@ -1,6 +1,7 @@
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
@@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) {
#endif
ESP_LOGI(TAG, "Beginning Deep Sleep");
if (this->sleep_duration_.has_value())
ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
App.run_safe_shutdown_hooks();

View File

@@ -9,6 +9,10 @@
#include <esp_sleep.h>
#endif
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome {
namespace deep_sleep {
@@ -116,15 +120,71 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
TEMPLATABLE_VALUE(uint32_t, sleep_duration);
#ifdef USE_TIME
void set_until(uint8_t hour, uint8_t minute, uint8_t second) {
this->hour_ = hour;
this->minute_ = minute;
this->second_ = second;
}
void set_time(time::RealTimeClock *time) { this->time_ = time; }
#endif
void play(Ts... x) override {
if (this->sleep_duration_.has_value()) {
this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
}
#ifdef USE_TIME
if (this->hour_.has_value()) {
auto time = this->time_->now();
const uint32_t timestamp_now = time.timestamp;
bool after_time = false;
if (time.hour > this->hour_) {
after_time = true;
} else {
if (time.hour == this->hour_) {
if (time.minute > this->minute_) {
after_time = true;
} else {
if (time.minute == this->minute_) {
if (time.second > this->second_) {
after_time = true;
}
}
}
}
}
time.hour = *this->hour_;
time.minute = *this->minute_;
time.second = *this->second_;
time.recalc_timestamp_utc();
time_t timestamp = time.timestamp; // timestamp in local time zone
if (after_time)
timestamp += 60 * 60 * 24;
int32_t offset = time::ESPTime::timezone_offset();
timestamp -= offset; // Change timestamp to utc
const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
this->deep_sleep_->set_sleep_duration(ms_left);
}
#endif
this->deep_sleep_->begin_sleep(true);
}
protected:
DeepSleepComponent *deep_sleep_;
#ifdef USE_TIME
optional<uint8_t> hour_;
optional<uint8_t> minute_;
optional<uint8_t> second_;
time::RealTimeClock *time_;
#endif
};
template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> {

View File

@@ -12,6 +12,7 @@ using namespace esphome::cover;
CoverTraits EndstopCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_position(true);
traits.set_supports_toggle(true);
traits.set_is_assumed_state(false);
return traits;
}
@@ -20,6 +21,20 @@ void EndstopCover::control(const CoverCall &call) {
this->start_direction_(COVER_OPERATION_IDLE);
this->publish_state();
}
if (call.get_toggle().has_value()) {
if (this->current_operation != COVER_OPERATION_IDLE) {
this->start_direction_(COVER_OPERATION_IDLE);
this->publish_state();
} else {
if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
this->target_position_ = COVER_OPEN;
this->start_direction_(COVER_OPERATION_OPENING);
} else {
this->target_position_ = COVER_CLOSED;
this->start_direction_(COVER_OPERATION_CLOSING);
}
}
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
if (pos == this->position) {
@@ -125,9 +140,11 @@ void EndstopCover::start_direction_(CoverOperation dir) {
trig = this->stop_trigger_;
break;
case COVER_OPERATION_OPENING:
this->last_operation_ = dir;
trig = this->open_trigger_;
break;
case COVER_OPERATION_CLOSING:
this->last_operation_ = dir;
trig = this->close_trigger_;
break;
default:

View File

@@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component {
uint32_t start_dir_time_{0};
uint32_t last_publish_time_{0};
float target_position_{0};
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
};
} // namespace endstop

View File

@@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_
default:
break;
}
for (auto *client : global_esp32_ble_tracker->clients_) {
client->gap_event_handler(event, param);
}
}
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {

View File

@@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener {
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
virtual void connect() = 0;
void set_state(ClientState st) { this->state_ = st; }
ClientState state() const { return state_; }

View File

@@ -1,12 +1,29 @@
import functools
from pathlib import Path
import hashlib
import re
import requests
from esphome import core
from esphome.components import display
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE
from esphome.const import (
CONF_FAMILY,
CONF_FILE,
CONF_GLYPHS,
CONF_ID,
CONF_RAW_DATA_ID,
CONF_TYPE,
CONF_SIZE,
CONF_PATH,
CONF_WEIGHT,
)
from esphome.core import CORE, HexInt
DOMAIN = "font"
DEPENDENCIES = ["display"]
MULTI_CONF = True
@@ -71,6 +88,128 @@ def validate_truetype_file(value):
return cv.file_(value)
def _compute_gfonts_local_path(value) -> Path:
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
h = hashlib.new("sha256")
h.update(name.encode())
return base_dir / h.hexdigest()[:8] / "font.ttf"
TYPE_LOCAL = "local"
TYPE_GFONTS = "gfonts"
LOCAL_SCHEMA = cv.Schema(
{
cv.Required(CONF_PATH): validate_truetype_file,
}
)
CONF_ITALIC = "italic"
FONT_WEIGHTS = {
"thin": 100,
"extra-light": 200,
"light": 300,
"regular": 400,
"medium": 500,
"semi-bold": 600,
"bold": 700,
"extra-bold": 800,
"black": 900,
}
def validate_weight_name(value):
return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)]
def download_gfonts(value):
wght = value[CONF_WEIGHT]
if value[CONF_ITALIC]:
wght = f"1,{wght}"
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}"
url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}"
path = _compute_gfonts_local_path(value)
if path.is_file():
return value
try:
req = requests.get(url)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(
f"Could not download font for {name}, please check the fonts exists "
f"at google fonts ({e})"
)
match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text)
if match is None:
raise cv.Invalid(
f"Could not extract ttf file from gfonts response for {name}, "
f"please report this."
)
ttf_url = match.group(1)
try:
req = requests.get(ttf_url)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}")
path.parent.mkdir(exist_ok=True, parents=True)
path.write_bytes(req.content)
return value
GFONTS_SCHEMA = cv.All(
{
cv.Required(CONF_FAMILY): cv.string_strict,
cv.Optional(CONF_WEIGHT, default="regular"): cv.Any(
cv.int_, validate_weight_name
),
cv.Optional(CONF_ITALIC, default=False): cv.boolean,
},
download_gfonts,
)
def validate_file_shorthand(value):
value = cv.string_strict(value)
if value.startswith("gfonts://"):
match = re.match(r"^gfonts://([^@]+)(@.+)?$", value)
if match is None:
raise cv.Invalid("Could not parse gfonts shorthand syntax, please check it")
family = match.group(1)
weight = match.group(2)
data = {
CONF_TYPE: TYPE_GFONTS,
CONF_FAMILY: family,
}
if weight is not None:
data[CONF_WEIGHT] = weight[1:]
return FILE_SCHEMA(data)
return FILE_SCHEMA(
{
CONF_TYPE: TYPE_LOCAL,
CONF_PATH: value,
}
)
TYPED_FILE_SCHEMA = cv.typed_schema(
{
TYPE_LOCAL: LOCAL_SCHEMA,
TYPE_GFONTS: GFONTS_SCHEMA,
}
)
def _file_schema(value):
if isinstance(value, str):
return validate_file_shorthand(value)
return TYPED_FILE_SCHEMA(value)
FILE_SCHEMA = cv.Schema(_file_schema)
DEFAULT_GLYPHS = (
' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
)
@@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id"
FONT_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(Font),
cv.Required(CONF_FILE): validate_truetype_file,
cv.Required(CONF_FILE): FILE_SCHEMA,
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
@@ -93,9 +232,13 @@ CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA)
async def to_code(config):
from PIL import ImageFont
path = CORE.relative_config_path(config[CONF_FILE])
conf = config[CONF_FILE]
if conf[CONF_TYPE] == TYPE_LOCAL:
path = CORE.relative_config_path(conf[CONF_PATH])
elif conf[CONF_TYPE] == TYPE_GFONTS:
path = _compute_gfonts_local_path(conf)
try:
font = ImageFont.truetype(path, config[CONF_SIZE])
font = ImageFont.truetype(str(path), config[CONF_SIZE])
except Exception as e:
raise core.EsphomeError(f"Could not load truetype file {path}: {e}")

View File

@@ -7,9 +7,11 @@ namespace growatt_solar {
static const char *const TAG = "growatt_solar";
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t MODBUS_REGISTER_COUNT = 33;
static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
void GrowattSolar::update() {
this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]);
}
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
@@ -27,37 +29,76 @@ void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
sensor->publish_state(value);
};
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
switch (this->protocol_version_) {
case RTU: {
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
break;
}
case RTU2: {
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 35, 36, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 37, TWO_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 38, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 39, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 40, 41, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 42, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 43, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 44, 45, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 46, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 47, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 48, 49, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->today_production_, 53, 54, ONE_DEC_UNIT);
publish_2_reg_sensor_state(this->total_energy_production_, 55, 56, ONE_DEC_UNIT);
publish_1_reg_sensor_state(this->inverter_module_temp_, 93, ONE_DEC_UNIT);
break;
}
}
}
void GrowattSolar::dump_config() {

View File

@@ -10,12 +10,19 @@ namespace growatt_solar {
static const float TWO_DEC_UNIT = 0.01;
static const float ONE_DEC_UNIT = 0.1;
enum GrowattProtocolVersion {
RTU = 0,
RTU2,
};
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
public:
void update() override;
void on_modbus_data(const std::vector<uint8_t> &data) override;
void dump_config() override;
void set_protocol_version(GrowattProtocolVersion protocol_version) { this->protocol_version_ = protocol_version; }
void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
@@ -67,6 +74,7 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
sensor::Sensor *today_production_{nullptr};
sensor::Sensor *total_energy_production_{nullptr};
sensor::Sensor *inverter_module_temp_{nullptr};
GrowattProtocolVersion protocol_version_;
};
} // namespace growatt_solar

View File

@@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA"
CONF_INVERTER_STATUS = "inverter_status"
CONF_PV_ACTIVE_POWER = "pv_active_power"
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
CONF_PROTOCOL_VERSION = "protocol_version"
AUTO_LOAD = ["modbus"]
CODEOWNERS = ["@leeuwte"]
@@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema(
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
)
GrowattProtocolVersion = growatt_solar_ns.enum("GrowattProtocolVersion")
PROTOCOL_VERSIONS = {
"RTU": GrowattProtocolVersion.RTU,
"RTU2": GrowattProtocolVersion.RTU2,
}
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GrowattSolar),
cv.Optional(CONF_PROTOCOL_VERSION, default="RTU"): cv.enum(
PROTOCOL_VERSIONS, upper=True
),
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
@@ -152,6 +162,8 @@ async def to_code(config):
await cg.register_component(var, config)
await modbus.register_modbus_device(var, config)
cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION]))
if CONF_INVERTER_STATUS in config:
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
cg.add(var.set_inverter_status_sensor(sens))

View File

@@ -25,6 +25,7 @@ PROTOCOLS = {
"daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417,
"daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480,
"daikin": Protocol.PROTOCOL_DAIKIN,
"electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL,
"fuego": Protocol.PROTOCOL_FUEGO,
"fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ,
"gree": Protocol.PROTOCOL_GREE,
@@ -112,6 +113,4 @@ def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
# PIO isn't updating releases, so referencing the release tag directly. See:
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
cg.add_library("tonia/HeatpumpIR", "1.0.20")

View File

@@ -20,6 +20,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT
{PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT
{PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT
{PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }}, // NOLINT
{PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT
{PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT

View File

@@ -20,6 +20,7 @@ enum Protocol {
PROTOCOL_DAIKIN_ARC417,
PROTOCOL_DAIKIN_ARC480,
PROTOCOL_DAIKIN,
PROTOCOL_ELECTROLUXYAL,
PROTOCOL_FUEGO,
PROTOCOL_FUJITSU_AWYZ,
PROTOCOL_GREE,

View File

@@ -7,7 +7,7 @@ namespace hm3301 {
class AbstractAQICalculator {
public:
virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
};
} // namespace hm3301

View File

@@ -7,7 +7,7 @@ namespace hm3301 {
class AQICalculator : public AbstractAQICalculator {
public:
uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);

View File

@@ -8,7 +8,7 @@ namespace hm3301 {
class CAQICalculator : public AbstractAQICalculator {
public:
uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);

View File

@@ -62,7 +62,7 @@ void HM3301Component::update() {
pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
}
int8_t aqi_value = -1;
int16_t aqi_value = -1;
if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);

View File

@@ -1,4 +1,20 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
CODEOWNERS = ["@OttoWinter"]
homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
{
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_ATTRIBUTE): cv.string,
cv.Optional(CONF_INTERNAL, default=True): cv.boolean,
}
)
def setup_home_assistant_entity(var, config):
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))

View File

@@ -1,30 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
from .. import homeassistant_ns
from .. import (
HOME_ASSISTANT_IMPORT_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
DEPENDENCIES = ["api"]
HomeassistantBinarySensor = homeassistant_ns.class_(
"HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONFIG_SCHEMA = (
binary_sensor.binary_sensor_schema(HomeassistantBinarySensor)
.extend(
{
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_ATTRIBUTE): cv.string,
}
)
.extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend(
HOME_ASSISTANT_IMPORT_SCHEMA
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
setup_home_assistant_entity(var, config)

View File

@@ -1,12 +1,11 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ATTRIBUTE,
CONF_ENTITY_ID,
CONF_ID,
from .. import (
HOME_ASSISTANT_IMPORT_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
from .. import homeassistant_ns
DEPENDENCIES = ["api"]
@@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_(
"HomeassistantSensor", sensor.Sensor, cg.Component
)
CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend(
{
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_ATTRIBUTE): cv.string,
}
CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend(
HOME_ASSISTANT_IMPORT_SCHEMA
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
setup_home_assistant_entity(var, config)

View File

@@ -1,9 +1,11 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
from .. import homeassistant_ns
from .. import (
HOME_ASSISTANT_IMPORT_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
DEPENDENCIES = ["api"]
@@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_(
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
{
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_ATTRIBUTE): cv.string,
}
CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend(
HOME_ASSISTANT_IMPORT_SCHEMA
)
async def to_code(config):
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
setup_home_assistant_entity(var, config)

View File

@@ -1,38 +0,0 @@
from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
)
from esphome.core import CORE
import esphome.config_validation as cv
import esphome.codegen as cg
from .const import KEY_HOST
# force import gpio to register pin schema
from .gpio import host_pin_to_code # noqa
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["network"]
def set_core_data(config):
CORE.data[KEY_HOST] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
return config
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
set_core_data,
)
async def to_code(config):
cg.add_build_flag("-DUSE_HOST")
cg.add_define("ESPHOME_BOARD", "host")
cg.add_platformio_option("platform", "platformio/native")

View File

@@ -1,5 +0,0 @@
import esphome.codegen as cg
KEY_HOST = "host"
host_ns = cg.esphome_ns.namespace("host")

View File

@@ -1,74 +0,0 @@
#ifdef USE_HOST
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "preferences.h"
#include <sched.h>
#include <time.h>
#include <cmath>
#include <cstdlib>
namespace esphome {
void IRAM_ATTR HOT yield() { ::sched_yield(); }
uint32_t IRAM_ATTR HOT millis() {
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
time_t seconds = spec.tv_sec;
uint32_t ms = round(spec.tv_nsec / 1e6);
return ((uint32_t) seconds) * 1000U + ms;
}
void IRAM_ATTR HOT delay(uint32_t ms) {
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
int res;
do {
res = nanosleep(&ts, &ts);
} while (res != 0 && errno == EINTR);
}
uint32_t IRAM_ATTR HOT micros() {
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
time_t seconds = spec.tv_sec;
uint32_t us = round(spec.tv_nsec / 1e3);
return ((uint32_t) seconds) * 1000000U + us;
}
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
struct timespec ts;
ts.tv_sec = us / 1000000U;
ts.tv_nsec = (us % 1000000U) * 1000U;
int res;
do {
res = nanosleep(&ts, &ts);
} while (res != 0 && errno == EINTR);
}
void arch_restart() { exit(0); }
void IRAM_ATTR HOT arch_feed_wdt() {
// pass
}
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() {
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
time_t seconds = spec.tv_sec;
uint32_t us = spec.tv_nsec;
return ((uint32_t) seconds) * 1000000000U + us;
}
uint32_t arch_get_cpu_freq_hz() { return 1000000000U; }
} // namespace esphome
void setup();
void loop();
int main() {
esphome::host::setup_preferences();
setup();
while (true) {
loop();
}
}
#endif // USE_HOST

View File

@@ -1,59 +0,0 @@
#ifdef USE_HOST
#include "gpio.h"
#include "esphome/core/log.h"
namespace esphome {
namespace host {
static const char *const TAG = "host";
struct ISRPinArg {
uint8_t pin;
bool inverted;
};
ISRInternalGPIOPin HostGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void HostGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const {
ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type);
}
void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
std::string HostGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
bool HostGPIOPin::digital_read() { return inverted_; }
void HostGPIOPin::digital_write(bool value) {
// pass
ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW");
}
void HostGPIOPin::detach_interrupt() const {}
} // namespace host
using namespace host;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return arg->inverted;
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
// pass
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
}
} // namespace esphome
#endif // USE_HOST

View File

@@ -1,37 +0,0 @@
#pragma once
#ifdef USE_HOST
#include "esphome/core/hal.h"
namespace esphome {
namespace host {
class HostGPIOPin : public InternalGPIOPin {
public:
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
void setup() override { pin_mode(flags_); }
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return pin_; }
bool is_inverted() const override { return inverted_; }
protected:
void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
} // namespace host
} // namespace esphome
#endif // USE_HOST

View File

@@ -1,73 +0,0 @@
import logging
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OPEN_DRAIN,
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
)
from esphome import pins
import esphome.config_validation as cv
import esphome.codegen as cg
from .const import host_ns
_LOGGER = logging.getLogger(__name__)
HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin)
def _translate_pin(value):
if isinstance(value, dict) or value is None:
raise cv.Invalid(
"This variable only supports pin numbers, not full pin schemas "
"(with inverted and mode)."
)
if isinstance(value, int):
return value
try:
return int(value)
except ValueError:
pass
if value.startswith("GPIO"):
return cv.int_(value[len("GPIO") :].strip())
return value
def validate_gpio_pin(value):
return _translate_pin(value)
HOST_PIN_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(HostGPIOPin),
cv.Required(CONF_NUMBER): validate_gpio_pin,
cv.Optional(CONF_MODE, default={}): cv.Schema(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
}
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
},
)
@pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA)
async def host_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -1,33 +0,0 @@
#ifdef USE_HOST
#include "preferences.h"
#include <cstring>
#include "esphome/core/preferences.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace host {
static const char *const TAG = "host.preferences";
class HostPreferences : public ESPPreferences {
public:
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
};
void setup_preferences() {
auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = pref;
}
} // namespace host
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome
#endif // USE_HOST

View File

@@ -1,13 +0,0 @@
#pragma once
#ifdef USE_HOST
namespace esphome {
namespace host {
void setup_preferences();
} // namespace host
} // namespace esphome
#endif // USE_HOST

View File

@@ -0,0 +1,11 @@
import esphome.codegen as cg
from esphome.components import uart
CODEOWNERS = ["@functionpointer"]
DEPENDENCIES = ["uart"]
hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx")
RGModel = hydreon_rgxx_ns.enum("RGModel")
HydreonRGxxComponent = hydreon_rgxx_ns.class_(
"HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice
)

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_COLD,
)
from . import hydreon_rgxx_ns, HydreonRGxxComponent
CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id"
CONF_TOO_COLD = "too_cold"
HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_(
"HydreonRGxxBinaryComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(HydreonRGxxBinarySensor),
cv.GenerateID(CONF_HYDREON_RGXX_ID): cv.use_id(HydreonRGxxComponent),
cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_COLD
),
}
)
async def to_code(config):
main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID])
bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor)
await cg.register_component(bin_component, config)
if CONF_TOO_COLD in config:
tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD])
cg.add(main_sensor.set_too_cold_sensor(tc))

View File

@@ -0,0 +1,211 @@
#include "hydreon_rgxx.h"
#include "esphome/core/log.h"
namespace esphome {
namespace hydreon_rgxx {
static const char *const TAG = "hydreon_rgxx.sensor";
static const int MAX_DATA_LENGTH_BYTES = 80;
static const uint8_t ASCII_LF = 0x0A;
#define HYDREON_RGXX_COMMA ,
static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
void HydreonRGxxComponent::dump_config() {
this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8);
ESP_LOGCONFIG(TAG, "hydreon_rgxx:");
if (this->is_failed()) {
ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
}
LOG_UPDATE_INTERVAL(this);
int i = 0;
#define HYDREON_RGXX_LOG_SENSOR(s) \
if (this->sensors_[i++] != nullptr) { \
LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
}
HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
}
void HydreonRGxxComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx...");
while (this->available() != 0) {
this->read();
}
this->schedule_reboot_();
}
bool HydreonRGxxComponent::sensor_missing_() {
if (this->sensors_received_ == -1) {
// no request sent yet, don't check
return false;
} else {
if (this->sensors_received_ == 0) {
ESP_LOGW(TAG, "No data at all");
return true;
}
for (int i = 0; i < NUM_SENSORS; i++) {
if (this->sensors_[i] == nullptr) {
continue;
}
if ((this->sensors_received_ >> i & 1) == 0) {
ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
return true;
}
}
return false;
}
}
void HydreonRGxxComponent::update() {
if (this->boot_count_ > 0) {
if (this->sensor_missing_()) {
this->no_response_count_++;
ESP_LOGE(TAG, "data missing %d times", this->no_response_count_);
if (this->no_response_count_ > 15) {
ESP_LOGE(TAG, "asking sensor to reboot");
for (auto &sensor : this->sensors_) {
if (sensor != nullptr) {
sensor->publish_state(NAN);
}
}
this->schedule_reboot_();
return;
}
} else {
this->no_response_count_ = 0;
}
this->write_str("R\n");
#ifdef USE_BINARY_SENSOR
if (this->too_cold_sensor_ != nullptr) {
this->too_cold_sensor_->publish_state(this->too_cold_);
}
#endif
this->too_cold_ = false;
this->sensors_received_ = 0;
}
}
void HydreonRGxxComponent::loop() {
uint8_t data;
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_ += (char) data;
if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) {
// complete line received
this->process_line_();
this->buffer_.clear();
}
}
}
}
/**
* Communication with the sensor is asynchronous.
* We send requests and let esphome continue doing its thing.
* Once we have received a complete line, we process it.
*
* Catching communication failures is done in two layers:
*
* 1. We check if all requested data has been received
* before we send out the next request. If data keeps
* missing, we escalate.
* 2. Request the sensor to reboot. We retry based on
* a timeout. If the sensor does not respond after
* several boot attempts, we give up.
*/
void HydreonRGxxComponent::schedule_reboot_() {
this->boot_count_ = 0;
this->set_interval("reboot", 5000, [this]() {
if (this->boot_count_ < 0) {
ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
}
this->boot_count_--;
this->write_str("K\n");
if (this->boot_count_ < -5) {
ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
for (auto &sensor : this->sensors_) {
if (sensor != nullptr) {
sensor->publish_state(NAN);
}
}
this->mark_failed();
}
});
}
bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
return this->buffer_starts_with_(prefix.c_str());
}
bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; }
void HydreonRGxxComponent::process_line_() {
ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
if (buffer_[0] == ';') {
ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
return;
}
if (this->buffer_starts_with_("PwrDays")) {
if (this->boot_count_ <= 0) {
this->boot_count_ = 1;
} else {
this->boot_count_++;
}
this->cancel_interval("reboot");
this->no_response_count_ = 0;
ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode
return;
}
if (this->buffer_starts_with_("SW")) {
std::string::size_type majend = this->buffer_.find('.');
std::string::size_type endversion = this->buffer_.find(' ', 3);
if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
}
int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
}
this->sw_version_ = major * 1000 + minor;
ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
return;
}
bool is_data_line = false;
for (int i = 0; i < NUM_SENSORS; i++) {
if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) {
is_data_line = true;
break;
}
}
if (is_data_line) {
std::string::size_type tc = this->buffer_.find("TooCold");
this->too_cold_ |= tc != std::string::npos;
if (this->too_cold_) {
ESP_LOGD(TAG, "Received TooCold");
}
for (int i = 0; i < NUM_SENSORS; i++) {
if (this->sensors_[i] == nullptr) {
continue;
}
std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
if (n == std::string::npos) {
continue;
}
int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10);
this->sensors_[i]->publish_state(data);
ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
this->sensors_received_ |= (1 << i);
}
} else {
ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
}
}
float HydreonRGxxComponent::get_setup_priority() const { return setup_priority::DATA; }
} // namespace hydreon_rgxx
} // namespace esphome

View File

@@ -0,0 +1,76 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace hydreon_rgxx {
enum RGModel {
RG9 = 1,
RG15 = 2,
};
#ifdef HYDREON_RGXX_NUM_SENSORS
static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS;
#else
static const uint8_t NUM_SENSORS = 1;
#endif
#ifndef HYDREON_RGXX_PROTOCOL_LIST
#define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("")
#endif
class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
public:
void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; }
#ifdef USE_BINARY_SENSOR
void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; }
#endif
void set_model(RGModel model) { model_ = model; }
/// Schedule data readings.
void update() override;
/// Read data once available
void loop() override;
/// Setup the sensor and test for a connection.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
void process_line_();
void schedule_reboot_();
bool buffer_starts_with_(const std::string &prefix);
bool buffer_starts_with_(const char *prefix);
bool sensor_missing_();
sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr};
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *too_cold_sensor_ = nullptr;
#endif
int16_t boot_count_ = 0;
int16_t no_response_count_ = 0;
std::string buffer_;
RGModel model_ = RG9;
int sw_version_ = 0;
bool too_cold_ = false;
// bit field showing which sensors we have received data for
int sensors_received_ = -1;
};
class HydreonRGxxBinaryComponent : public Component {
public:
HydreonRGxxBinaryComponent(HydreonRGxxComponent *parent) {}
};
} // namespace hydreon_rgxx
} // namespace esphome

View File

@@ -0,0 +1,119 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart, sensor
from esphome.const import (
CONF_ID,
CONF_MODEL,
CONF_MOISTURE,
DEVICE_CLASS_HUMIDITY,
STATE_CLASS_MEASUREMENT,
)
from . import RGModel, HydreonRGxxComponent
UNIT_INTENSITY = "intensity"
UNIT_MILLIMETERS = "mm"
UNIT_MILLIMETERS_PER_HOUR = "mm/h"
CONF_ACC = "acc"
CONF_EVENT_ACC = "event_acc"
CONF_TOTAL_ACC = "total_acc"
CONF_R_INT = "r_int"
RG_MODELS = {
"RG_9": RGModel.RG9,
"RG_15": RGModel.RG15,
# https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
}
SUPPORTED_SENSORS = {
CONF_ACC: ["RG_15"],
CONF_EVENT_ACC: ["RG_15"],
CONF_TOTAL_ACC: ["RG_15"],
CONF_R_INT: ["RG_15"],
CONF_MOISTURE: ["RG_9"],
}
PROTOCOL_NAMES = {
CONF_MOISTURE: "R",
CONF_ACC: "Acc",
CONF_R_INT: "Rint",
CONF_EVENT_ACC: "EventAcc",
CONF_TOTAL_ACC: "TotalAcc",
}
def _validate(config):
for conf, models in SUPPORTED_SENSORS.items():
if conf in config:
if config[CONF_MODEL] not in models:
raise cv.Invalid(
f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}"
)
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HydreonRGxxComponent),
cv.Required(CONF_MODEL): cv.enum(
RG_MODELS,
upper=True,
space="_",
),
cv.Optional(CONF_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_R_INT): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_INTENSITY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA),
_validate,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add_define(
"HYDREON_RGXX_PROTOCOL_LIST(F, sep)",
cg.RawExpression(
" sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()])
),
)
cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES))
for i, conf in enumerate(PROTOCOL_NAMES):
if conf in config:
sens = await sensor.new_sensor(config[conf])
cg.add(var.set_sensor(sens, i))

View File

@@ -46,21 +46,21 @@ class I2CDevice {
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) {
ErrorCode err = this->write(&a_register, 1);
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) {
ErrorCode err = this->write(&a_register, 1, stop);
if (err != ERROR_OK)
return err;
return this->read(data, len);
}
ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); }
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) {
ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); }
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true) {
WriteBuffer buffers[2];
buffers[0].data = &a_register;
buffers[0].len = 1;
buffers[1].data = data;
buffers[1].len = len;
return bus_->writev(address_, buffers, 2);
return bus_->writev(address_, buffers, 2, stop);
}
// Compat APIs
@@ -93,7 +93,9 @@ class I2CDevice {
return true;
}
bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) {
return read_register(a_register, data, 1, stop) == ERROR_OK;
}
optional<uint8_t> read_byte(uint8_t a_register) {
uint8_t data;
@@ -104,8 +106,8 @@ class I2CDevice {
bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) {
return write_register(a_register, data, len) == ERROR_OK;
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) {
return write_register(a_register, data, len, stop) == ERROR_OK;
}
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
@@ -118,7 +120,9 @@ class I2CDevice {
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); }
bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) {
return write_bytes(a_register, &data, 1, stop);
}
bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }

View File

@@ -15,6 +15,7 @@ enum ErrorCode {
ERROR_NOT_INITIALIZED = 4,
ERROR_TOO_LARGE = 5,
ERROR_UNKNOWN = 6,
ERROR_CRC = 7,
};
struct ReadBuffer {
@@ -36,12 +37,18 @@ class I2CBus {
}
virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0;
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
return write(address, buffer, len, true);
}
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) {
WriteBuffer buf;
buf.data = buffer;
buf.len = len;
return writev(address, &buf, 1);
return writev(address, &buf, 1, stop);
}
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
return writev(address, buffers, cnt, true);
}
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0;
protected:
void i2c_scan_() {

View File

@@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt)
return ERROR_OK;
}
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
@@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
return ERROR_UNKNOWN;
}
}
uint8_t status = wire_->endTransmission(true);
uint8_t status = wire_->endTransmission(stop);
if (status == 0) {
return ERROR_OK;
} else if (status == 1) {

View File

@@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
void setup() override;
void dump_config() override;
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_scan(bool scan) { scan_ = scan; }

View File

@@ -142,7 +142,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
return ERROR_OK;
}
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {

View File

@@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component {
void setup() override;
void dump_config() override;
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_scan(bool scan) { scan_ = scan; }

View File

@@ -16,16 +16,24 @@ static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT
std::string build_json(const json_build_t &f) {
// Here we are allocating as much heap memory as available minus 2kb to be safe
// Here we are allocating up to 5kb of memory,
// with the heap size minus 2kb to be safe if less than 5kb
// as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
#endif
DynamicJsonDocument json_document(free_heap);
const size_t request_size = std::min(free_heap - 2048, (size_t) 5120);
DynamicJsonDocument json_document(request_size);
if (json_document.memoryPool().buffer() == nullptr) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes",
request_size, free_heap);
return "{}";
}
JsonObject root = json_document.to<JsonObject>();
f(root);
json_document.shrinkToFit();
@@ -36,27 +44,45 @@ std::string build_json(const json_build_t &f) {
}
void parse_json(const std::string &data, const json_parse_t &f) {
// Here we are allocating as much heap memory as available minus 2kb to be safe
// Here we are allocating 1.5 times the data size,
// with the heap size minus 2kb to be safe if less than that
// as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
#endif
bool pass = false;
size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5));
do {
DynamicJsonDocument json_document(request_size);
if (json_document.memoryPool().buffer() == nullptr) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
free_heap);
return;
}
DeserializationError err = deserializeJson(json_document, data);
json_document.shrinkToFit();
DynamicJsonDocument json_document(free_heap);
DeserializationError err = deserializeJson(json_document, data);
json_document.shrinkToFit();
JsonObject root = json_document.as<JsonObject>();
JsonObject root = json_document.as<JsonObject>();
if (err) {
ESP_LOGW(TAG, "Parsing JSON failed.");
return;
}
f(root);
if (err == DeserializationError::Ok) {
pass = true;
f(root);
} else if (err == DeserializationError::NoMemory) {
if (request_size * 2 >= free_heap) {
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
return;
}
ESP_LOGV(TAG, "Increasing memory allocation.");
request_size *= 2;
continue;
} else {
ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
return;
}
} while (!pass);
}
} // namespace json

View File

@@ -1,7 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display
from esphome.const import CONF_DIMENSIONS
from esphome.const import CONF_DIMENSIONS, CONF_POSITION, CONF_DATA
CONF_USER_CHARACTERS = "user_characters"
lcd_base_ns = cg.esphome_ns.namespace("lcd_base")
LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent)
@@ -16,9 +18,35 @@ def validate_lcd_dimensions(value):
return value
def validate_user_characters(value):
positions = set()
for conf in value:
if conf[CONF_POSITION] in positions:
raise cv.Invalid(
f"Duplicate user defined character at position {conf[CONF_POSITION]}"
)
positions.add(conf[CONF_POSITION])
return value
LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
{
cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
cv.Optional(CONF_USER_CHARACTERS): cv.All(
cv.ensure_list(
cv.Schema(
{
cv.Required(CONF_POSITION): cv.int_range(min=0, max=7),
cv.Required(CONF_DATA): cv.All(
cv.ensure_list(cv.int_range(min=0, max=31)),
cv.Length(min=8, max=8),
),
}
),
),
cv.Length(max=8),
validate_user_characters,
),
}
).extend(cv.polling_component_schema("1s"))
@@ -27,3 +55,6 @@ async def setup_lcd_display(var, config):
await cg.register_component(var, config)
await display.register_display(var, config)
cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
if CONF_USER_CHARACTERS in config:
for usr in config[CONF_USER_CHARACTERS]:
cg.add(var.set_user_defined_char(usr[CONF_POSITION], usr[CONF_DATA]))

View File

@@ -65,6 +65,13 @@ void LCDDisplay::setup() {
this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
}
// store user defined characters
for (auto &user_defined_char : this->user_defined_chars_) {
this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (user_defined_char.first << 3));
for (auto data : user_defined_char.second)
this->send(data, true);
}
this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
uint8_t display_control = LCD_DISPLAY_DISPLAY_ON;
this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control);
@@ -160,6 +167,13 @@ void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time:
}
void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); }
#endif
void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7
this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3));
for (int i = 0; i < 8; i++) {
this->send(charmap[i], true);
}
}
} // namespace lcd_base
} // namespace esphome

View File

@@ -7,6 +7,8 @@
#include "esphome/components/time/real_time_clock.h"
#endif
#include <map>
namespace esphome {
namespace lcd_base {
@@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent {
this->rows_ = rows;
}
void set_user_defined_char(uint8_t pos, const std::vector<uint8_t> &data) { this->user_defined_chars_[pos] = data; }
void setup() override;
float get_setup_priority() const override;
void update() override;
@@ -47,6 +51,9 @@ class LCDDisplay : public PollingComponent {
void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
/// Load custom char to given location
void loadchar(uint8_t location, uint8_t charmap[]);
protected:
virtual bool is_four_bit_mode() = 0;
virtual void write_n_bits(uint8_t value, uint8_t n) = 0;
@@ -58,6 +65,7 @@ class LCDDisplay : public PollingComponent {
uint8_t columns_;
uint8_t rows_;
uint8_t *buffer_{nullptr};
std::map<uint8_t, std::vector<uint8_t> > user_defined_chars_;
};
} // namespace lcd_base

View File

@@ -113,9 +113,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.SplitDefault(CONF_HARDWARE_UART, esp32="UART0", esp8266="UART0"): cv.All(
cv.only_on(["esp32", "esp8266"]), uart_selection
),
cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection,
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
cv.Optional(CONF_LOGS, default={}): cv.Schema(
{
@@ -140,9 +138,12 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(90.0)
async def to_code(config):
baud_rate = config[CONF_BAUD_RATE]
log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
if CONF_HARDWARE_UART in config:
cg.add(log.set_uart_selection(config[CONF_HARDWARE_UART]))
rhs = Logger.new(
baud_rate,
config[CONF_TX_BUFFER_SIZE],
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
)
log = cg.Pvariable(config[CONF_ID], rhs)
cg.add(log.pre_setup())
for tag, level in config[CONF_LOGS].items():
@@ -202,15 +203,6 @@ async def to_code(config):
)
def maybe_simple_message(schema):
def validator(value):
if isinstance(value, dict):
return cv.Schema(schema)(value)
return cv.Schema(schema)({CONF_FORMAT: value})
return validator
def validate_printf(value):
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
cfmt = r"""
@@ -233,7 +225,7 @@ def validate_printf(value):
CONF_LOGGER_LOG = "logger.log"
LOGGER_LOG_ACTION_SCHEMA = cv.All(
maybe_simple_message(
cv.maybe_simple_value(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
@@ -241,9 +233,10 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All(
*LOG_LEVEL_TO_ESP_LOG, upper=True
),
cv.Optional(CONF_TAG, default="main"): cv.string,
}
),
validate_printf,
},
validate_printf,
key=CONF_FORMAT,
)
)

View File

@@ -130,14 +130,12 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
if (xPortGetFreeHeapSize() < 2048)
return;
#endif
#ifdef USE_HOST
puts(msg);
#endif
this->log_callback_.call(level, tag, msg);
}
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
// add 1 to buffer size for null terminator
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
}
@@ -219,11 +217,7 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const std::string &tag, int log_level) {
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
}
#if defined(USE_ESP32) || defined(USE_ESP8266)
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback));
}
@@ -239,10 +233,7 @@ void Logger::dump_config() {
ESP_LOGCONFIG(TAG, "Logger:");
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
ESP_LOGCONFIG(TAG, " Log Baud Rate: %u", this->baud_rate_);
#if defined(USE_ESP32) || defined(USE_ESP8266)
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
#endif
for (auto &it : this->log_levels_) {
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
}

View File

@@ -17,7 +17,6 @@ namespace esphome {
namespace logger {
#if defined(USE_ESP32) || defined(USE_ESP8266)
/** Enum for logging UART selection
*
* Advanced configuration (pin selection, etc) is not supported.
@@ -32,11 +31,10 @@ enum UARTSelection {
UART_SELECTION_UART0_SWAP,
#endif
};
#endif // USE_ESP32 || USE_ESP8266
class Logger : public Component {
public:
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
/// Manually set the baud rate for serial, set to 0 to disable.
void set_baud_rate(uint32_t baud_rate);
@@ -48,11 +46,8 @@ class Logger : public Component {
uart_port_t get_uart_num() const { return uart_num_; }
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266)
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
/// Get the UART used by the logger.
UARTSelection get_uart() const;
#endif
/// Set the log level of the specified tag.
void set_log_level(const std::string &tag, int log_level);
@@ -122,9 +117,7 @@ class Logger : public Component {
char *tx_buffer_{nullptr};
int tx_buffer_at_{0};
int tx_buffer_size_{0};
#if defined(USE_ESP32) || defined(USE_ESP8266)
UARTSelection uart_{UART_SELECTION_UART0};
#endif
#ifdef USE_ARDUINO
HardwareSerial *hw_serial_{nullptr};
#endif

View File

@@ -20,7 +20,7 @@ void MCP3204::dump_config() {
}
float MCP3204::read_data(uint8_t pin) {
uint8_t adc_primary_config = 0b00000110 & 0b00000111;
uint8_t adc_primary_config = 0b00000110 | (pin >> 2);
uint8_t adc_secondary_config = pin << 6;
this->enable();
this->transfer_byte(adc_primary_config);

View File

@@ -17,7 +17,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MCP3204Sensor),
cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=3),
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7),
}
).extend(cv.polling_component_schema("60s"))

View File

@@ -71,9 +71,9 @@ SENSOR_VALUE_TYPE = {
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORDU_R": SensorValueType.U_QWORD_R,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"U_QWORD_R": SensorValueType.S_QWORD_R,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
@@ -87,9 +87,9 @@ TYPE_REGISTER_MAP = {
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORDU_R": 4,
"S_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}

View File

@@ -455,6 +455,28 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
return cmd;
}
ModbusCommandItem ModbusCommandItem::create_custom_command(
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
&&handler) {
ModbusCommandItem cmd = {};
cmd.modbusdevice = modbusdevice;
cmd.function_code = ModbusFunctionCode::CUSTOM;
if (handler == nullptr) {
cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
ESP_LOGI(TAG, "Custom Command sent");
};
} else {
cmd.on_data_func = handler;
}
for (auto v : values) {
cmd.payload.push_back((v >> 8) & 0xFF);
cmd.payload.push_back(v & 0xFF);
}
return cmd;
}
bool ModbusCommandItem::send() {
if (this->function_code != ModbusFunctionCode::CUSTOM) {
modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),

View File

@@ -2,12 +2,12 @@
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/modbus/modbus.h"
#include "esphome/core/automation.h"
#include <list>
#include <set>
#include <queue>
#include <set>
#include <vector>
namespace esphome {
@@ -374,8 +374,8 @@ class ModbusCommandItem {
const std::vector<bool> &values);
/** Create custom modbus command
* @param modbusdevice pointer to the device to execute the command
* @param values byte vector of data to be sent to the device. The compplete payload must be provided with the
* exception of the crc codess
* @param values byte vector of data to be sent to the device. The complete payload must be provided with the
* exception of the crc codes
* @param handler function called when the response is received. Default is just logging a response
* @return ModbusCommandItem with the prepared command
*/
@@ -383,6 +383,18 @@ class ModbusCommandItem {
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
&&handler = nullptr);
/** Create custom modbus command
* @param modbusdevice pointer to the device to execute the command
* @param values word vector of data to be sent to the device. The complete payload must be provided with the
* exception of the crc codes
* @param handler function called when the response is received. Default is just logging a response
* @return ModbusCommandItem with the prepared command
*/
static ModbusCommandItem create_custom_command(
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
&&handler = nullptr);
};
/** Modbus controller class.

View File

@@ -11,6 +11,7 @@ from esphome.const import (
)
from .. import (
MODBUS_WRITE_REGISTER_TYPE,
add_modbus_base_properties,
modbus_controller_ns,
modbus_calc_properties,
@@ -24,6 +25,7 @@ from ..const import (
CONF_CUSTOM_COMMAND,
CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID,
CONF_REGISTER_TYPE,
CONF_SKIP_UPDATES,
CONF_USE_WRITE_MULTIPLE,
CONF_VALUE_TYPE,
@@ -61,6 +63,9 @@ CONFIG_SCHEMA = cv.All(
number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend(
{
cv.GenerateID(): cv.declare_id(ModbusNumber),
cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum(
MODBUS_WRITE_REGISTER_TYPE
),
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
# 24 bits are the maximum value for fp32 before precison is lost
@@ -81,6 +86,7 @@ async def to_code(config):
byte_offset, reg_count = modbus_calc_properties(config)
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_REGISTER_TYPE],
config[CONF_ADDRESS],
byte_offset,
config[CONF_BITMASK],

View File

@@ -26,6 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
}
void ModbusNumber::control(float value) {
ModbusCommandItem write_cmd;
std::vector<uint16_t> data;
float write_value = value;
// Is there are lambda configured?
@@ -45,33 +46,39 @@ void ModbusNumber::control(float value) {
write_value = multiply_by_ * write_value;
}
// lambda didn't set payload
if (data.empty()) {
data = float_to_payload(write_value, this->sensor_value_type);
}
ESP_LOGD(TAG,
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
// Create and send the write command
ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
write_cmd =
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
if (!data.empty()) {
ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str());
write_cmd = ModbusCommandItem::create_custom_command(
this->parent_, data,
[this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data);
});
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
this->register_count, data);
data = float_to_payload(write_value, this->sensor_value_type);
ESP_LOGD(TAG,
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
// Create and send the write command
if (this->register_count == 1 && !this->use_write_multiple_) {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
write_cmd =
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
this->register_count, data);
}
// publish new value
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
// gets called when the write command is ack'd from the device
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->publish_state(value);
};
}
// publish new value
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
// gets called when the write command is ack'd from the device
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->publish_state(value);
};
parent_->queue_command(write_cmd);
this->publish_state(value);
}
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }

View File

@@ -11,9 +11,9 @@ using value_to_data_t = std::function<float>(float);
class ModbusNumber : public number::Number, public Component, public SensorItem {
public:
ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count,
uint8_t skip_updates, bool force_new_range) {
this->register_type = ModbusRegisterType::HOLDING;
ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) {
this->register_type = register_type;
this->start_address = start_address;
this->offset = offset;
this->bitmask = bitmask;

View File

@@ -1,8 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import select
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA
from esphome.jsonschema import jschema_composite
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
from .. import (
SENSOR_VALUE_TYPE,
@@ -30,7 +29,6 @@ ModbusSelect = modbus_controller_ns.class_(
)
@jschema_composite
def ensure_option_map():
def validator(value):
cv.check_not_templatable(value)
@@ -79,6 +77,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
cv.Required(CONF_OPTIONSMAP): ensure_option_map(),
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
},
@@ -112,6 +111,7 @@ async def to_code(config):
cg.add(parent.add_sensor_item(var))
cg.add(var.set_parent(parent))
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(

View File

@@ -80,6 +80,9 @@ void ModbusSelect::control(const std::string &value) {
}
parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(value);
}
} // namespace modbus_controller

View File

@@ -32,6 +32,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
@@ -43,6 +44,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
std::vector<int64_t> mapping_;
ModbusController *parent_;
bool use_write_multiple_{false};
bool optimistic_{false};
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
};

View File

@@ -9,6 +9,7 @@ from esphome.const import (
CONF_AVAILABILITY,
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_CERTIFICATE_AUTHORITY,
CONF_CLIENT_ID,
CONF_COMMAND_TOPIC,
CONF_COMMAND_RETAIN,
@@ -42,9 +43,14 @@ from esphome.const import (
CONF_WILL_MESSAGE,
)
from esphome.core import coroutine_with_priority, CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["network"]
AUTO_LOAD = ["json", "async_tcp"]
AUTO_LOAD = ["json"]
CONF_IDF_SEND_ASYNC = "idf_send_async"
CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check"
def validate_message_just_topic(value):
@@ -163,6 +169,15 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_USERNAME, default=""): cv.string,
cv.Optional(CONF_PASSWORD, default=""): cv.string,
cv.Optional(CONF_CLIENT_ID): cv.string,
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
),
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
cv.string, cv.only_with_esp_idf
),
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
),
cv.Optional(CONF_DISCOVERY, default=True): cv.Any(
cv.boolean, cv.one_of("CLEAN", upper=True)
),
@@ -217,7 +232,6 @@ CONFIG_SCHEMA = cv.All(
}
),
validate_config,
cv.only_with_arduino,
)
@@ -238,9 +252,11 @@ def exp_mqtt_message(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Add required libraries for arduino
if CORE.using_arduino:
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
cg.add_define("USE_MQTT")
cg.add_global(mqtt_ns.using)
@@ -321,6 +337,19 @@ async def to_code(config):
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
# esp-idf only
if CONF_CERTIFICATE_AUTHORITY in config:
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
# prevent error -0x428e
# See https://github.com/espressif/esp-idf/issues/139
add_idf_sdkconfig_option("CONFIG_MBEDTLS_HARDWARE_MPI", False)
if CONF_IDF_SEND_ASYNC in config and config[CONF_IDF_SEND_ASYNC]:
cg.add_define("USE_MQTT_IDF_ENQUEUE")
# end esp-idf
for conf in config.get(CONF_ON_MESSAGE, []):
trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC])
cg.add(trig.set_qos(conf[CONF_QOS]))

View File

@@ -0,0 +1,69 @@
#pragma once
#include <string>
#include <map>
#include "esphome/components/network/ip_address.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace mqtt {
enum class MQTTClientDisconnectReason : int8_t {
TCP_DISCONNECTED = 0,
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
MQTT_IDENTIFIER_REJECTED = 2,
MQTT_SERVER_UNAVAILABLE = 3,
MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7
};
/// internal struct for MQTT messages.
struct MQTTMessage {
std::string topic;
std::string payload;
uint8_t qos; ///< QoS. Only for last will testaments.
bool retain;
};
class MQTTBackend {
public:
using on_connect_callback_t = void(bool session_present);
using on_disconnect_callback_t = void(MQTTClientDisconnectReason reason);
using on_subscribe_callback_t = void(uint16_t packet_id, uint8_t qos);
using on_unsubscribe_callback_t = void(uint16_t packet_id);
using on_message_callback_t = void(const char *topic, const char *payload, size_t len, size_t index, size_t total);
using on_publish_user_callback_t = void(uint16_t packet_id);
virtual void set_keep_alive(uint16_t keep_alive) = 0;
virtual void set_client_id(const char *client_id) = 0;
virtual void set_clean_session(bool clean_session) = 0;
virtual void set_credentials(const char *username, const char *password) = 0;
virtual void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) = 0;
virtual void set_server(network::IPAddress ip, uint16_t port) = 0;
virtual void set_server(const char *host, uint16_t port) = 0;
virtual void set_on_connect(std::function<on_connect_callback_t> &&callback) = 0;
virtual void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) = 0;
virtual void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) = 0;
virtual void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) = 0;
virtual void set_on_message(std::function<on_message_callback_t> &&callback) = 0;
virtual void set_on_publish(std::function<on_publish_user_callback_t> &&callback) = 0;
virtual bool connected() const = 0;
virtual void connect() = 0;
virtual void disconnect() = 0;
virtual bool subscribe(const char *topic, uint8_t qos) = 0;
virtual bool unsubscribe(const char *topic) = 0;
virtual bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) = 0;
virtual bool publish(const MQTTMessage &message) {
return publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
message.retain);
}
// called from MQTTClient::loop()
virtual void loop() {}
};
} // namespace mqtt
} // namespace esphome

View File

@@ -0,0 +1,74 @@
#pragma once
#ifdef USE_ARDUINO
#include "mqtt_backend.h"
#include <AsyncMqttClient.h>
namespace esphome {
namespace mqtt {
class MQTTBackendArduino final : public MQTTBackend {
public:
void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); }
void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); }
void set_clean_session(bool clean_session) final { mqtt_client_.setCleanSession(clean_session); }
void set_credentials(const char *username, const char *password) final {
mqtt_client_.setCredentials(username, password);
}
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
mqtt_client_.setWill(topic, qos, retain, payload);
}
void set_server(network::IPAddress ip, uint16_t port) final {
mqtt_client_.setServer(IPAddress(static_cast<uint32_t>(ip)), port);
}
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
#if ASYNC_TCP_SSL_ENABLED
void set_secure(bool secure) { mqtt_client.setSecure(secure); }
void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); }
#endif
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
this->mqtt_client_.onConnect(std::move(callback));
}
void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final {
auto async_callback = [callback](AsyncMqttClientDisconnectReason reason) {
// int based enum so casting isn't a problem
callback(static_cast<MQTTClientDisconnectReason>(reason));
};
this->mqtt_client_.onDisconnect(std::move(async_callback));
}
void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final {
this->mqtt_client_.onSubscribe(std::move(callback));
}
void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final {
this->mqtt_client_.onUnsubscribe(std::move(callback));
}
void set_on_message(std::function<on_message_callback_t> &&callback) final {
auto async_callback = [callback](const char *topic, const char *payload,
AsyncMqttClientMessageProperties async_properties, size_t len, size_t index,
size_t total) { callback(topic, payload, len, index, total); };
mqtt_client_.onMessage(std::move(async_callback));
}
void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final {
this->mqtt_client_.onPublish(std::move(callback));
}
bool connected() const final { return mqtt_client_.connected(); }
void connect() final { mqtt_client_.connect(); }
void disconnect() final { mqtt_client_.disconnect(true); }
bool subscribe(const char *topic, uint8_t qos) final { return mqtt_client_.subscribe(topic, qos) != 0; }
bool unsubscribe(const char *topic) final { return mqtt_client_.unsubscribe(topic) != 0; }
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final {
return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0;
}
using MQTTBackend::publish;
protected:
AsyncMqttClient mqtt_client_;
};
} // namespace mqtt
} // namespace esphome
#endif // defined(USE_ARDUINO)

View File

@@ -0,0 +1,149 @@
#ifdef USE_ESP_IDF
#include <string>
#include "mqtt_backend_idf.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.idf";
bool MQTTBackendIDF::initialize_() {
mqtt_cfg_.user_context = (void *) this;
mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE;
mqtt_cfg_.host = this->host_.c_str();
mqtt_cfg_.port = this->port_;
mqtt_cfg_.keepalive = this->keep_alive_;
mqtt_cfg_.disable_clean_session = !this->clean_session_;
if (!this->username_.empty()) {
mqtt_cfg_.username = this->username_.c_str();
if (!this->password_.empty()) {
mqtt_cfg_.password = this->password_.c_str();
}
}
if (!this->lwt_topic_.empty()) {
mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str();
this->mqtt_cfg_.lwt_qos = this->lwt_qos_;
this->mqtt_cfg_.lwt_retain = this->lwt_retain_;
if (!this->lwt_message_.empty()) {
mqtt_cfg_.lwt_msg = this->lwt_message_.c_str();
mqtt_cfg_.lwt_msg_len = this->lwt_message_.size();
}
}
if (!this->client_id_.empty()) {
mqtt_cfg_.client_id = this->client_id_.c_str();
}
if (ca_certificate_.has_value()) {
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
} else {
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
}
auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_);
if (mqtt_client) {
handler_.reset(mqtt_client);
is_initalized_ = true;
esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, this);
return true;
} else {
ESP_LOGE(TAG, "Failed to initialize IDF-MQTT");
return false;
}
}
void MQTTBackendIDF::loop() {
// process new events
// handle only 1 message per loop iteration
if (!mqtt_events_.empty()) {
auto &event = mqtt_events_.front();
mqtt_event_handler_(event);
mqtt_events_.pop();
}
}
void MQTTBackendIDF::mqtt_event_handler_(const esp_mqtt_event_t &event) {
ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id);
switch (event.event_id) {
case MQTT_EVENT_BEFORE_CONNECT:
ESP_LOGV(TAG, "MQTT_EVENT_BEFORE_CONNECT");
break;
case MQTT_EVENT_CONNECTED:
ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED");
// TODO session present check
this->is_connected_ = true;
this->on_connect_.call(!mqtt_cfg_.disable_clean_session);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED");
// TODO is there a way to get the disconnect reason?
this->is_connected_ = false;
this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED);
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGV(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event.msg_id);
// hardcode QoS to 0. QoS is not used in this context but required to mirror the AsyncMqtt interface
this->on_subscribe_.call((int) event.msg_id, 0);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGV(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event.msg_id);
this->on_unsubscribe_.call((int) event.msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGV(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event.msg_id);
this->on_publish_.call((int) event.msg_id);
break;
case MQTT_EVENT_DATA: {
static std::string topic;
if (event.topic) {
// not 0 terminated - create a string from it
topic = std::string(event.topic, event.topic_len);
}
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
auto data_len = event.data_len;
if (data_len == 0)
data_len = strlen(event.data);
this->on_message_.call(event.topic ? const_cast<char *>(topic.c_str()) : nullptr, event.data, data_len,
event.current_data_offset, event.total_data_len);
} break;
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
if (event.error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle->esp_tls_last_esp_err);
ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle->esp_tls_stack_err);
ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle->esp_transport_sock_errno,
strerror(event.error_handle->esp_transport_sock_errno));
} else if (event.error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle->connect_return_code);
} else {
ESP_LOGE(TAG, "Unknown error type: 0x%x", event.error_handle->error_type);
}
break;
default:
ESP_LOGV(TAG, "Other event id:%d", event.event_id);
break;
}
}
/// static - Dispatch event to instance method
void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
MQTTBackendIDF *instance = static_cast<MQTTBackendIDF *>(handler_args);
// queue event to decouple processing
if (instance) {
auto event = *static_cast<esp_mqtt_event_t *>(event_data);
instance->mqtt_events_.push(event);
}
}
} // namespace mqtt
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,143 @@
#pragma once
#ifdef USE_ESP_IDF
#include <string>
#include <queue>
#include <mqtt_client.h>
#include "esphome/components/network/ip_address.h"
#include "esphome/core/helpers.h"
#include "mqtt_backend.h"
namespace esphome {
namespace mqtt {
class MQTTBackendIDF final : public MQTTBackend {
public:
static const size_t MQTT_BUFFER_SIZE = 4096;
void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; }
void set_client_id(const char *client_id) final { this->client_id_ = client_id; }
void set_clean_session(bool clean_session) final { this->clean_session_ = clean_session; }
void set_credentials(const char *username, const char *password) final {
if (username)
this->username_ = username;
if (password)
this->password_ = password;
}
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
if (topic)
this->lwt_topic_ = topic;
this->lwt_qos_ = qos;
if (payload)
this->lwt_message_ = payload;
this->lwt_retain_ = retain;
}
void set_server(network::IPAddress ip, uint16_t port) final {
this->host_ = ip.str();
this->port_ = port;
}
void set_server(const char *host, uint16_t port) final {
this->host_ = host;
this->port_ = port;
}
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
this->on_connect_.add(std::move(callback));
}
void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final {
this->on_disconnect_.add(std::move(callback));
}
void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final {
this->on_subscribe_.add(std::move(callback));
}
void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final {
this->on_unsubscribe_.add(std::move(callback));
}
void set_on_message(std::function<on_message_callback_t> &&callback) final {
this->on_message_.add(std::move(callback));
}
void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final {
this->on_publish_.add(std::move(callback));
}
bool connected() const final { return this->is_connected_; }
void connect() final {
if (!is_initalized_) {
if (initialize_()) {
esp_mqtt_client_start(handler_.get());
}
}
}
void disconnect() final {
if (is_initalized_)
esp_mqtt_client_disconnect(handler_.get());
}
bool subscribe(const char *topic, uint8_t qos) final {
return esp_mqtt_client_subscribe(handler_.get(), topic, qos) != -1;
}
bool unsubscribe(const char *topic) final { return esp_mqtt_client_unsubscribe(handler_.get(), topic) != -1; }
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final {
#if defined(USE_MQTT_IDF_ENQUEUE)
// use the non-blocking version
// it can delay sending a couple of seconds but won't block
return esp_mqtt_client_enqueue(handler_.get(), topic, payload, length, qos, retain, true) != -1;
#else
// might block for several seconds, either due to network timeout (10s)
// or if publishing payloads longer than internal buffer (due to message fragmentation)
return esp_mqtt_client_publish(handler_.get(), topic, payload, length, qos, retain) != -1;
#endif
}
using MQTTBackend::publish;
void loop() final;
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
protected:
bool initialize_();
void mqtt_event_handler_(const esp_mqtt_event_t &event);
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
struct MqttClientDeleter {
void operator()(esp_mqtt_client *client_handler) { esp_mqtt_client_destroy(client_handler); }
};
using ClientHandler_ = std::unique_ptr<esp_mqtt_client, MqttClientDeleter>;
ClientHandler_ handler_;
bool is_connected_{false};
bool is_initalized_{false};
esp_mqtt_client_config_t mqtt_cfg_{};
std::string host_;
uint16_t port_;
std::string username_;
std::string password_;
std::string lwt_topic_;
std::string lwt_message_;
uint8_t lwt_qos_;
bool lwt_retain_;
std::string client_id_;
uint16_t keep_alive_;
bool clean_session_;
optional<std::string> ca_certificate_;
bool skip_cert_cn_check_{false};
// callbacks
CallbackManager<on_connect_callback_t> on_connect_;
CallbackManager<on_disconnect_callback_t> on_disconnect_;
CallbackManager<on_subscribe_callback_t> on_subscribe_;
CallbackManager<on_unsubscribe_callback_t> on_unsubscribe_;
CallbackManager<on_message_callback_t> on_message_;
CallbackManager<on_publish_user_callback_t> on_publish_;
std::queue<esp_mqtt_event_t> mqtt_events_;
};
} // namespace mqtt
} // namespace esphome
#endif

View File

@@ -27,21 +27,21 @@ MQTTClientComponent::MQTTClientComponent() {
// Connection
void MQTTClientComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up MQTT...");
this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties,
size_t len, size_t index, size_t total) {
if (index == 0)
this->payload_buffer_.reserve(total);
this->mqtt_backend_.set_on_message(
[this](const char *topic, const char *payload, size_t len, size_t index, size_t total) {
if (index == 0)
this->payload_buffer_.reserve(total);
// append new payload, may contain incomplete MQTT message
this->payload_buffer_.append(payload, len);
// append new payload, may contain incomplete MQTT message
this->payload_buffer_.append(payload, len);
// MQTT fully received
if (len + index == total) {
this->on_message(topic, this->payload_buffer_);
this->payload_buffer_.clear();
}
});
this->mqtt_client_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
// MQTT fully received
if (len + index == total) {
this->on_message(topic, this->payload_buffer_);
this->payload_buffer_.clear();
}
});
this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) {
this->state_ = MQTT_CLIENT_DISCONNECTED;
this->disconnect_reason_ = reason;
});
@@ -49,8 +49,10 @@ void MQTTClientComponent::setup() {
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
if (level <= this->log_level_ && this->is_connected()) {
this->publish(this->log_message_.topic, message, strlen(message), this->log_message_.qos,
this->log_message_.retain);
this->publish({.topic = this->log_message_.topic,
.payload = message,
.qos = this->log_message_.qos,
.retain = this->log_message_.retain});
}
});
}
@@ -173,9 +175,9 @@ void MQTTClientComponent::start_connect_() {
ESP_LOGI(TAG, "Connecting to MQTT...");
// Force disconnect first
this->mqtt_client_.disconnect(true);
this->mqtt_backend_.disconnect();
this->mqtt_client_.setClientId(this->credentials_.client_id.c_str());
this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str());
const char *username = nullptr;
if (!this->credentials_.username.empty())
username = this->credentials_.username.c_str();
@@ -183,24 +185,24 @@ void MQTTClientComponent::start_connect_() {
if (!this->credentials_.password.empty())
password = this->credentials_.password.c_str();
this->mqtt_client_.setCredentials(username, password);
this->mqtt_backend_.set_credentials(username, password);
this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port);
this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port);
if (!this->last_will_.topic.empty()) {
this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
this->last_will_.payload.c_str(), this->last_will_.payload.length());
this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
this->last_will_.payload.c_str());
}
this->mqtt_client_.connect();
this->mqtt_backend_.connect();
this->state_ = MQTT_CLIENT_CONNECTING;
this->connect_begin_ = millis();
}
bool MQTTClientComponent::is_connected() {
return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_client_.connected();
return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_backend_.connected();
}
void MQTTClientComponent::check_connected() {
if (!this->mqtt_client_.connected()) {
if (!this->mqtt_backend_.connected()) {
if (millis() - this->connect_begin_ > 60000) {
this->state_ = MQTT_CLIENT_DISCONNECTED;
this->start_dnslookup_();
@@ -222,31 +224,34 @@ void MQTTClientComponent::check_connected() {
}
void MQTTClientComponent::loop() {
// Call the backend loop first
mqtt_backend_.loop();
if (this->disconnect_reason_.has_value()) {
const LogString *reason_s;
switch (*this->disconnect_reason_) {
case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED:
case MQTTClientDisconnectReason::TCP_DISCONNECTED:
reason_s = LOG_STR("TCP disconnected");
break;
case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
case MQTTClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
reason_s = LOG_STR("Unacceptable Protocol Version");
break;
case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
case MQTTClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
reason_s = LOG_STR("Identifier Rejected");
break;
case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
case MQTTClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
reason_s = LOG_STR("Server Unavailable");
break;
case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
case MQTTClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
reason_s = LOG_STR("Malformed Credentials");
break;
case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED:
case MQTTClientDisconnectReason::MQTT_NOT_AUTHORIZED:
reason_s = LOG_STR("Not Authorized");
break;
case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
case MQTTClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
reason_s = LOG_STR("Not Enough Space");
break;
case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT:
case MQTTClientDisconnectReason::TLS_BAD_FINGERPRINT:
reason_s = LOG_STR("TLS Bad Fingerprint");
break;
default:
@@ -275,7 +280,7 @@ void MQTTClientComponent::loop() {
this->check_connected();
break;
case MQTT_CLIENT_CONNECTED:
if (!this->mqtt_client_.connected()) {
if (!this->mqtt_backend_.connected()) {
this->state_ = MQTT_CLIENT_DISCONNECTED;
ESP_LOGW(TAG, "Lost MQTT Client connection!");
this->start_dnslookup_();
@@ -302,10 +307,10 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) {
if (!this->is_connected())
return false;
uint16_t ret = this->mqtt_client_.subscribe(topic, qos);
bool ret = this->mqtt_backend_.subscribe(topic, qos);
yield();
if (ret != 0) {
if (ret) {
ESP_LOGV(TAG, "subscribe(topic='%s')", topic);
} else {
delay(5);
@@ -360,9 +365,9 @@ void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_js
}
void MQTTClientComponent::unsubscribe(const std::string &topic) {
uint16_t ret = this->mqtt_client_.unsubscribe(topic.c_str());
bool ret = this->mqtt_backend_.unsubscribe(topic.c_str());
yield();
if (ret != 0) {
if (ret) {
ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str());
} else {
delay(5);
@@ -387,34 +392,35 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
bool retain) {
return publish({.topic = topic, .payload = payload, .qos = qos, .retain = retain});
}
bool MQTTClientComponent::publish(const MQTTMessage &message) {
if (!this->is_connected()) {
// critical components will re-transmit their messages
return false;
}
bool logging_topic = topic == this->log_message_.topic;
uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length);
bool logging_topic = this->log_message_.topic == message.topic;
bool ret = this->mqtt_backend_.publish(message);
delay(0);
if (ret == 0 && !logging_topic && this->is_connected()) {
if (!ret && !logging_topic && this->is_connected()) {
delay(0);
ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length);
ret = this->mqtt_backend_.publish(message);
delay(0);
}
if (!logging_topic) {
if (ret != 0) {
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain);
if (ret) {
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(),
message.retain);
} else {
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(),
payload_length); // NOLINT
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(),
message.payload.length());
this->status_momentary_warning("publish", 1000);
}
}
return ret != 0;
}
bool MQTTClientComponent::publish(const MQTTMessage &message) {
return this->publish(message.topic, message.payload, message.qos, message.retain);
}
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
bool retain) {
std::string message = json::build_json(f);
@@ -499,10 +505,10 @@ bool MQTTClientComponent::is_log_message_enabled() const { return !this->log_mes
void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); }
void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; }
void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_client_.setKeepAlive(keep_alive_s); }
void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_backend_.set_keep_alive(keep_alive_s); }
void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); }
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
void MQTTClientComponent::set_topic_prefix(std::string topic_prefix) { this->topic_prefix_ = std::move(topic_prefix); }
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; }
const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
void MQTTClientComponent::disable_birth_message() {
this->birth_message_.topic = "";
@@ -549,7 +555,8 @@ void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscovery
void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; }
void MQTTClientComponent::disable_discovery() {
this->discovery_info_ = MQTTDiscoveryInfo{.prefix = "", .retain = false};
this->discovery_info_ = MQTTDiscoveryInfo{
.prefix = "", .retain = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR};
}
void MQTTClientComponent::on_shutdown() {
if (!this->shutdown_message_.topic.empty()) {
@@ -557,13 +564,13 @@ void MQTTClientComponent::on_shutdown() {
this->publish(this->shutdown_message_);
yield();
}
this->mqtt_client_.disconnect(true);
this->mqtt_backend_.disconnect();
}
#if ASYNC_TCP_SSL_ENABLED
void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) {
this->mqtt_client_.setSecure(true);
this->mqtt_client_.addServerFingerprint(fingerprint.data());
this->mqtt_backend_.setSecure(true);
this->mqtt_backend_.addServerFingerprint(fingerprint.data());
}
#endif

View File

@@ -9,7 +9,11 @@
#include "esphome/core/log.h"
#include "esphome/components/json/json_util.h"
#include "esphome/components/network/ip_address.h"
#include <AsyncMqttClient.h>
#if defined(USE_ESP_IDF)
#include "mqtt_backend_idf.h"
#elif defined(USE_ARDUINO)
#include "mqtt_backend_arduino.h"
#endif
#include "lwip/ip_addr.h"
namespace esphome {
@@ -22,14 +26,6 @@ namespace mqtt {
using mqtt_callback_t = std::function<void(const std::string &, const std::string &)>;
using mqtt_json_callback_t = std::function<void(const std::string &, JsonObject)>;
/// internal struct for MQTT messages.
struct MQTTMessage {
std::string topic;
std::string payload;
uint8_t qos; ///< QoS. Only for last will testaments.
bool retain;
};
/// internal struct for MQTT subscriptions.
struct MQTTSubscription {
std::string topic;
@@ -139,7 +135,10 @@ class MQTTClientComponent : public Component {
*/
void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint);
#endif
#ifdef USE_ESP_IDF
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
#endif
const Availability &get_availability();
/** Set the topic prefix that will be prepended to all topics together with "/". This will, in most cases,
@@ -150,7 +149,7 @@ class MQTTClientComponent : public Component {
*
* @param topic_prefix The topic prefix. The last "/" is appended automatically.
*/
void set_topic_prefix(std::string topic_prefix);
void set_topic_prefix(const std::string &topic_prefix);
/// Get the topic prefix of this device, using default if necessary
const std::string &get_topic_prefix() const;
@@ -277,6 +276,7 @@ class MQTTClientComponent : public Component {
.prefix = "homeassistant",
.retain = true,
.clean = false,
.unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR,
};
std::string topic_prefix_{};
MQTTMessage log_message_;
@@ -284,7 +284,12 @@ class MQTTClientComponent : public Component {
int log_level_{ESPHOME_LOG_LEVEL};
std::vector<MQTTSubscription> subscriptions_;
AsyncMqttClient mqtt_client_;
#if defined(USE_ESP_IDF)
MQTTBackendIDF mqtt_backend_;
#elif defined(USE_ARDUINO)
MQTTBackendArduino mqtt_backend_;
#endif
MQTTClientState state_{MQTT_CLIENT_DISCONNECTED};
network::IPAddress ip_;
bool dns_resolved_{false};
@@ -293,7 +298,7 @@ class MQTTClientComponent : public Component {
uint32_t reboot_timeout_{300000};
uint32_t connect_begin_;
uint32_t last_connected_{0};
optional<AsyncMqttClientDisconnectReason> disconnect_reason_{};
optional<MQTTClientDisconnectReason> disconnect_reason_{};
};
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -1,3 +1,4 @@
from esphome import automation
import esphome.codegen as cg
CODEOWNERS = ["@jesserockz"]
@@ -5,3 +6,7 @@ CODEOWNERS = ["@jesserockz"]
nfc_ns = cg.esphome_ns.namespace("nfc")
NfcTag = nfc_ns.class_("NfcTag")
NfcOnTagTrigger = nfc_ns.class_(
"NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag)
)

View File

@@ -0,0 +1,9 @@
#include "automation.h"
namespace esphome {
namespace nfc {
void NfcOnTagTrigger::process(const std::unique_ptr<NfcTag> &tag) { this->trigger(format_uid(tag->get_uid()), *tag); }
} // namespace nfc
} // namespace esphome

View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include "esphome/core/automation.h"
#include "nfc.h"
namespace esphome {
namespace nfc {
class NfcOnTagTrigger : public Trigger<std::string, NfcTag> {
public:
void process(const std::unique_ptr<NfcTag> &tag);
};
} // namespace nfc
} // namespace esphome

View File

@@ -63,8 +63,8 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.float_,
cv.Optional(CONF_BELOW): cv.float_,
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),

View File

@@ -473,6 +473,8 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_
App.reboot();
});
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
delay(300); // NOLINT
App.setup();
ESP_LOGI(TAG, "Waiting for OTA attempt.");

View File

@@ -14,9 +14,6 @@ CONF_ON_FINISHED_WRITE = "on_finished_write"
pn532_ns = cg.esphome_ns.namespace("pn532")
PN532 = pn532_ns.class_("PN532", cg.PollingComponent)
PN532OnTagTrigger = pn532_ns.class_(
"PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag)
)
PN532OnFinishedWriteTrigger = pn532_ns.class_(
"PN532OnFinishedWriteTrigger", automation.Trigger.template()
)
@@ -30,7 +27,7 @@ PN532_SCHEMA = cv.Schema(
cv.GenerateID(): cv.declare_id(PN532),
cv.Optional(CONF_ON_TAG): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
}
),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
@@ -42,7 +39,7 @@ PN532_SCHEMA = cv.Schema(
),
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
}
),
}

View File

@@ -144,9 +144,9 @@ void PN532::loop() {
}
if (nfcid.size() == this->current_uid_.size()) {
bool same_uid = false;
bool same_uid = true;
for (size_t i = 0; i < nfcid.size(); i++)
same_uid |= nfcid[i] == this->current_uid_[i];
same_uid &= nfcid[i] == this->current_uid_[i];
if (same_uid)
return;
}
@@ -376,9 +376,6 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
this->found_ = true;
return true;
}
void PN532OnTagTrigger::process(const std::unique_ptr<nfc::NfcTag> &tag) {
this->trigger(nfc::format_uid(tag->get_uid()), *tag);
}
} // namespace pn532
} // namespace esphome

View File

@@ -5,6 +5,7 @@
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/nfc/nfc_tag.h"
#include "esphome/components/nfc/nfc.h"
#include "esphome/components/nfc/automation.h"
namespace esphome {
namespace pn532 {
@@ -16,7 +17,6 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
class PN532BinarySensor;
class PN532OnTagTrigger;
class PN532 : public PollingComponent {
public:
@@ -30,8 +30,8 @@ class PN532 : public PollingComponent {
void loop() override;
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
void register_ontag_trigger(PN532OnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
void register_ontagremoved_trigger(PN532OnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
void add_on_finished_write_callback(std::function<void()> callback) {
this->on_finished_write_callback_.add(std::move(callback));
@@ -79,8 +79,8 @@ class PN532 : public PollingComponent {
bool requested_read_{false};
std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<PN532OnTagTrigger *> triggers_ontag_;
std::vector<PN532OnTagTrigger *> triggers_ontagremoved_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
std::vector<uint8_t> current_uid_;
nfc::NdefMessage *next_task_message_to_write_;
enum NfcTask {
@@ -115,11 +115,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
bool found_{false};
};
class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> {
public:
void process(const std::unique_ptr<nfc::NfcTag> &tag);
};
class PN532OnFinishedWriteTrigger : public Trigger<> {
public:
explicit PN532OnFinishedWriteTrigger(PN532 *parent) {

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@andrewpc"]

View File

@@ -0,0 +1,397 @@
#include "qmp6988.h"
#include <cmath>
namespace esphome {
namespace qmp6988 {
static const uint8_t QMP6988_CHIP_ID = 0x5C;
static const uint8_t QMP6988_CHIP_ID_REG = 0xD1; /* Chip ID confirmation Register */
static const uint8_t QMP6988_RESET_REG = 0xE0; /* Device reset register */
static const uint8_t QMP6988_DEVICE_STAT_REG = 0xF3; /* Device state register */
static const uint8_t QMP6988_CTRLMEAS_REG = 0xF4; /* Measurement Condition Control Register */
/* data */
static const uint8_t QMP6988_PRESSURE_MSB_REG = 0xF7; /* Pressure MSB Register */
static const uint8_t QMP6988_TEMPERATURE_MSB_REG = 0xFA; /* Temperature MSB Reg */
/* compensation calculation */
static const uint8_t QMP6988_CALIBRATION_DATA_START = 0xA0; /* QMP6988 compensation coefficients */
static const uint8_t QMP6988_CALIBRATION_DATA_LENGTH = 25;
static const uint8_t SHIFT_RIGHT_4_POSITION = 4;
static const uint8_t SHIFT_LEFT_2_POSITION = 2;
static const uint8_t SHIFT_LEFT_4_POSITION = 4;
static const uint8_t SHIFT_LEFT_5_POSITION = 5;
static const uint8_t SHIFT_LEFT_8_POSITION = 8;
static const uint8_t SHIFT_LEFT_12_POSITION = 12;
static const uint8_t SHIFT_LEFT_16_POSITION = 16;
/* power mode */
static const uint8_t QMP6988_SLEEP_MODE = 0x00;
static const uint8_t QMP6988_FORCED_MODE = 0x01;
static const uint8_t QMP6988_NORMAL_MODE = 0x03;
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_POS = 0;
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_MSK = 0x03;
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_LEN = 2;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_POS = 5;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_MSK = 0xE0;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_LEN = 3;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_POS = 2;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_MSK = 0x1C;
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_LEN = 3;
static const uint8_t QMP6988_CONFIG_REG = 0xF1; /*IIR filter co-efficient setting Register*/
static const uint8_t QMP6988_CONFIG_REG_FILTER_POS = 0;
static const uint8_t QMP6988_CONFIG_REG_FILTER_MSK = 0x07;
static const uint8_t QMP6988_CONFIG_REG_FILTER_LEN = 3;
static const uint32_t SUBTRACTOR = 8388608;
static const char *const TAG = "qmp6988";
static const char *oversampling_to_str(QMP6988Oversampling oversampling) {
switch (oversampling) {
case QMP6988_OVERSAMPLING_SKIPPED:
return "None";
case QMP6988_OVERSAMPLING_1X:
return "1x";
case QMP6988_OVERSAMPLING_2X:
return "2x";
case QMP6988_OVERSAMPLING_4X:
return "4x";
case QMP6988_OVERSAMPLING_8X:
return "8x";
case QMP6988_OVERSAMPLING_16X:
return "16x";
case QMP6988_OVERSAMPLING_32X:
return "32x";
case QMP6988_OVERSAMPLING_64X:
return "64x";
default:
return "UNKNOWN";
}
}
static const char *iir_filter_to_str(QMP6988IIRFilter filter) {
switch (filter) {
case QMP6988_IIR_FILTER_OFF:
return "OFF";
case QMP6988_IIR_FILTER_2X:
return "2x";
case QMP6988_IIR_FILTER_4X:
return "4x";
case QMP6988_IIR_FILTER_8X:
return "8x";
case QMP6988_IIR_FILTER_16X:
return "16x";
case QMP6988_IIR_FILTER_32X:
return "32x";
default:
return "UNKNOWN";
}
}
bool QMP6988Component::device_check_() {
uint8_t ret = 0;
ret = this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1);
if (ret != i2c::ERROR_OK) {
ESP_LOGE(TAG, "%s: read chip ID (0xD1) failed", __func__);
}
ESP_LOGD(TAG, "qmp6988 read chip id = 0x%x", qmp6988_data_.chip_id);
return qmp6988_data_.chip_id == QMP6988_CHIP_ID;
}
bool QMP6988Component::get_calibration_data_() {
uint8_t status = 0;
// BITFIELDS temp_COE;
uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0};
int len;
for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) {
status = this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1);
if (status != i2c::ERROR_OK) {
ESP_LOGE(TAG, "qmp6988 read calibration data (0xA0) error!");
return false;
}
}
qmp6988_data_.qmp6988_cali.COE_a0 =
(QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) |
(a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) | (a_data_uint8_tr[24] & 0x0f))
<< 12);
qmp6988_data_.qmp6988_cali.COE_a0 = qmp6988_data_.qmp6988_cali.COE_a0 >> 12;
qmp6988_data_.qmp6988_cali.COE_a1 =
(QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[21]);
qmp6988_data_.qmp6988_cali.COE_a2 =
(QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[23]);
qmp6988_data_.qmp6988_cali.COE_b00 =
(QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) | (a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) |
((a_data_uint8_tr[24] & 0xf0) >> SHIFT_RIGHT_4_POSITION))
<< 12);
qmp6988_data_.qmp6988_cali.COE_b00 = qmp6988_data_.qmp6988_cali.COE_b00 >> 12;
qmp6988_data_.qmp6988_cali.COE_bt1 =
(QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[3]);
qmp6988_data_.qmp6988_cali.COE_bt2 =
(QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[5]);
qmp6988_data_.qmp6988_cali.COE_bp1 =
(QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[7]);
qmp6988_data_.qmp6988_cali.COE_b11 =
(QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[9]);
qmp6988_data_.qmp6988_cali.COE_bp2 =
(QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[11]);
qmp6988_data_.qmp6988_cali.COE_b12 =
(QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[13]);
qmp6988_data_.qmp6988_cali.COE_b21 =
(QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[15]);
qmp6988_data_.qmp6988_cali.COE_bp3 =
(QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[17]);
ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n");
ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0,
qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00);
ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1,
qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11);
ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2,
qmp6988_data_.qmp6988_cali.COE_b12, qmp6988_data_.qmp6988_cali.COE_b21, qmp6988_data_.qmp6988_cali.COE_bp3);
ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n");
qmp6988_data_.ik.a0 = qmp6988_data_.qmp6988_cali.COE_a0; // 20Q4
qmp6988_data_.ik.b00 = qmp6988_data_.qmp6988_cali.COE_b00; // 20Q4
qmp6988_data_.ik.a1 = 3608L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L; // 31Q23
qmp6988_data_.ik.a2 = 16889L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L; // 30Q47
qmp6988_data_.ik.bt1 = 2982L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L; // 28Q15
qmp6988_data_.ik.bt2 = 329854L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L; // 34Q38
qmp6988_data_.ik.bp1 = 19923L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L; // 31Q20
qmp6988_data_.ik.b11 = 2406L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L; // 28Q34
qmp6988_data_.ik.bp2 = 3079L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L; // 29Q43
qmp6988_data_.ik.b12 = 6846L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53
qmp6988_data_.ik.b21 = 13836L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60
qmp6988_data_.ik.bp3 = 2915L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65
ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n");
ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2,
qmp6988_data_.ik.b00);
ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2,
qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11);
ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12,
qmp6988_data_.ik.b21, qmp6988_data_.ik.bp3);
ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n");
return true;
}
QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt) {
QMP6988_S16_t ret;
QMP6988_S64_t wk1, wk2;
// wk1: 60Q4 // bit size
wk1 = ((QMP6988_S64_t) ik->a1 * (QMP6988_S64_t) dt); // 31Q23+24-1=54 (54Q23)
wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33)
wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23)
wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04)
ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0
return ret;
}
QMP6988_S32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx) {
QMP6988_S32_t ret;
QMP6988_S64_t wk1, wk2, wk3;
// wk1 = 48Q16 // bit size
wk1 = ((QMP6988_S64_t) ik->bt1 * (QMP6988_S64_t) tx); // 28Q15+16-1=43 (43Q15)
wk2 = ((QMP6988_S64_t) ik->bp1 * (QMP6988_S64_t) dp) >> 5; // 31Q20+24-1=54 (49Q15)
wk1 += wk2; // 43,49->50Q15
wk2 = ((QMP6988_S64_t) ik->bt2 * (QMP6988_S64_t) tx) >> 1; // 34Q38+16-1=49 (48Q37)
wk2 = (wk2 * (QMP6988_S64_t) tx) >> 8; // 48Q37+16-1=63 (55Q29)
wk3 = wk2; // 55Q29
wk2 = ((QMP6988_S64_t) ik->b11 * (QMP6988_S64_t) tx) >> 4; // 28Q34+16-1=43 (39Q30)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29)
wk3 += wk2; // 55,61->62Q29
wk2 = ((QMP6988_S64_t) ik->bp2 * (QMP6988_S64_t) dp) >> 13; // 29Q43+24-1=52 (39Q30)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29)
wk3 += wk2; // 62,61->63Q29
wk1 += wk3 >> 14; // Q29 >> 14 -> Q15
wk2 = ((QMP6988_S64_t) ik->b12 * (QMP6988_S64_t) tx); // 29Q53+16-1=45 (45Q53)
wk2 = (wk2 * (QMP6988_S64_t) tx) >> 22; // 45Q53+16-1=61 (39Q31)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q30)
wk3 = wk2; // 61Q30
wk2 = ((QMP6988_S64_t) ik->b21 * (QMP6988_S64_t) tx) >> 6; // 29Q60+16-1=45 (39Q54)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q54+24-1=62 (39Q31)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q20)
wk3 += wk2; // 61,61->62Q30
wk2 = ((QMP6988_S64_t) ik->bp3 * (QMP6988_S64_t) dp) >> 12; // 28Q65+24-1=51 (39Q53)
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q53+24-1=62 (39Q30)
wk2 = (wk2 * (QMP6988_S64_t) dp); // 39Q30+24-1=62 (62Q30)
wk3 += wk2; // 62,62->63Q30
wk1 += wk3 >> 15; // Q30 >> 15 = Q15
wk1 /= 32767L;
wk1 >>= 11; // Q15 >> 7 = Q4
wk1 += ik->b00; // Q4 + 20Q4
// wk1 >>= 4; // 28Q4 -> 24Q0
ret = (QMP6988_S32_t) wk1;
return ret;
}
void QMP6988Component::software_reset_() {
uint8_t ret = 0;
ret = this->write_byte(QMP6988_RESET_REG, 0xe6);
if (ret != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Software Reset (0xe6) failed");
}
delay(10);
this->write_byte(QMP6988_RESET_REG, 0x00);
}
void QMP6988Component::set_power_mode_(uint8_t power_mode) {
uint8_t data;
ESP_LOGD(TAG, "Setting Power mode to: %d", power_mode);
qmp6988_data_.power_mode = power_mode;
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
data = data & 0xfc;
if (power_mode == QMP6988_SLEEP_MODE) {
data |= 0x00;
} else if (power_mode == QMP6988_FORCED_MODE) {
data |= 0x01;
} else if (power_mode == QMP6988_NORMAL_MODE) {
data |= 0x03;
}
this->write_byte(QMP6988_CTRLMEAS_REG, data);
ESP_LOGD(TAG, "Set Power mode 0xf4=0x%x \r\n", data);
delay(10);
}
void QMP6988Component::write_filter_(unsigned char filter) {
uint8_t data;
data = (filter & 0x03);
this->write_byte(QMP6988_CONFIG_REG, data);
delay(10);
}
void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p) {
uint8_t data;
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
data &= 0xe3;
data |= (oversampling_p << 2);
this->write_byte(QMP6988_CTRLMEAS_REG, data);
delay(10);
}
void QMP6988Component::write_oversampling_temperature_(unsigned char oversampling_t) {
uint8_t data;
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
data &= 0x1f;
data |= (oversampling_t << 5);
this->write_byte(QMP6988_CTRLMEAS_REG, data);
delay(10);
}
void QMP6988Component::set_temperature_oversampling(QMP6988Oversampling oversampling_t) {
this->temperature_oversampling_ = oversampling_t;
}
void QMP6988Component::set_pressure_oversampling(QMP6988Oversampling oversampling_p) {
this->pressure_oversampling_ = oversampling_p;
}
void QMP6988Component::set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; }
void QMP6988Component::calculate_altitude_(float pressure, float temp) {
float altitude;
altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065;
this->qmp6988_data_.altitude = altitude;
}
void QMP6988Component::calculate_pressure_() {
uint8_t err = 0;
QMP6988_U32_t p_read, t_read;
QMP6988_S32_t p_raw, t_raw;
uint8_t a_data_uint8_tr[6] = {0};
QMP6988_S32_t t_int, p_int;
this->qmp6988_data_.temperature = 0;
this->qmp6988_data_.pressure = 0;
err = this->read_register(QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
if (err != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error reading raw pressure/temp values");
return;
}
p_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) |
(((QMP6988_U16_t)(a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2]));
p_raw = (QMP6988_S32_t)(p_read - SUBTRACTOR);
t_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) |
(((QMP6988_U16_t)(a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5]));
t_raw = (QMP6988_S32_t)(t_read - SUBTRACTOR);
t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw);
p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int);
this->qmp6988_data_.temperature = (float) t_int / 256.0f;
this->qmp6988_data_.pressure = (float) p_int / 16.0f;
}
void QMP6988Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up QMP6988");
bool ret;
ret = this->device_check_();
if (!ret) {
ESP_LOGCONFIG(TAG, "Setup failed - device not found");
}
this->software_reset_();
this->get_calibration_data_();
this->set_power_mode_(QMP6988_NORMAL_MODE);
this->write_filter_(iir_filter_);
this->write_oversampling_pressure_(this->pressure_oversampling_);
this->write_oversampling_temperature_(this->temperature_oversampling_);
}
void QMP6988Component::dump_config() {
ESP_LOGCONFIG(TAG, "QMP6988:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with QMP6988 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
}
float QMP6988Component::get_setup_priority() const { return setup_priority::DATA; }
void QMP6988Component::update() {
this->calculate_pressure_();
float pressurehectopascals = this->qmp6988_data_.pressure / 100;
float temperature = this->qmp6988_data_.temperature;
ESP_LOGD(TAG, "Temperature=%.2f°C, Pressure=%.2fhPa", temperature, pressurehectopascals);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
this->pressure_sensor_->publish_state(pressurehectopascals);
}
} // namespace qmp6988
} // namespace esphome

View File

@@ -0,0 +1,116 @@
#pragma once
#include "esphome/core/log.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace qmp6988 {
#define QMP6988_U16_t unsigned short
#define QMP6988_S16_t short
#define QMP6988_U32_t unsigned int
#define QMP6988_S32_t int
#define QMP6988_U64_t unsigned long long
#define QMP6988_S64_t long long
/* oversampling */
enum QMP6988Oversampling {
QMP6988_OVERSAMPLING_SKIPPED = 0x00,
QMP6988_OVERSAMPLING_1X = 0x01,
QMP6988_OVERSAMPLING_2X = 0x02,
QMP6988_OVERSAMPLING_4X = 0x03,
QMP6988_OVERSAMPLING_8X = 0x04,
QMP6988_OVERSAMPLING_16X = 0x05,
QMP6988_OVERSAMPLING_32X = 0x06,
QMP6988_OVERSAMPLING_64X = 0x07,
};
/* filter */
enum QMP6988IIRFilter {
QMP6988_IIR_FILTER_OFF = 0x00,
QMP6988_IIR_FILTER_2X = 0x01,
QMP6988_IIR_FILTER_4X = 0x02,
QMP6988_IIR_FILTER_8X = 0x03,
QMP6988_IIR_FILTER_16X = 0x04,
QMP6988_IIR_FILTER_32X = 0x05,
};
using qmp6988_cali_data_t = struct Qmp6988CaliData {
QMP6988_S32_t COE_a0;
QMP6988_S16_t COE_a1;
QMP6988_S16_t COE_a2;
QMP6988_S32_t COE_b00;
QMP6988_S16_t COE_bt1;
QMP6988_S16_t COE_bt2;
QMP6988_S16_t COE_bp1;
QMP6988_S16_t COE_b11;
QMP6988_S16_t COE_bp2;
QMP6988_S16_t COE_b12;
QMP6988_S16_t COE_b21;
QMP6988_S16_t COE_bp3;
};
using qmp6988_fk_data_t = struct Qmp6988FkData {
float a0, b00;
float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
};
using qmp6988_ik_data_t = struct Qmp6988IkData {
QMP6988_S32_t a0, b00;
QMP6988_S32_t a1, a2;
QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
};
using qmp6988_data_t = struct Qmp6988Data {
uint8_t chip_id;
uint8_t power_mode;
float temperature;
float pressure;
float altitude;
qmp6988_cali_data_t qmp6988_cali;
qmp6988_ik_data_t ik;
};
class QMP6988Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_iir_filter(QMP6988IIRFilter iirfilter);
void set_temperature_oversampling(QMP6988Oversampling oversampling_t);
void set_pressure_oversampling(QMP6988Oversampling oversampling_p);
protected:
qmp6988_data_t qmp6988_data_;
sensor::Sensor *temperature_sensor_;
sensor::Sensor *pressure_sensor_;
QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X};
QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X};
QMP6988IIRFilter iir_filter_{QMP6988_IIR_FILTER_OFF};
void software_reset_();
bool get_calibration_data_();
bool device_check_();
void set_power_mode_(uint8_t power_mode);
void write_oversampling_temperature_(unsigned char oversampling_t);
void write_oversampling_pressure_(unsigned char oversampling_p);
void write_filter_(unsigned char filter);
void calculate_pressure_();
void calculate_altitude_(float pressure, float temp);
QMP6988_S32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx);
QMP6988_S16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt);
};
} // namespace qmp6988
} // namespace esphome

View File

@@ -0,0 +1,101 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
)
DEPENDENCIES = ["i2c"]
qmp6988_ns = cg.esphome_ns.namespace("qmp6988")
QMP6988Component = qmp6988_ns.class_(
"QMP6988Component", cg.PollingComponent, i2c.I2CDevice
)
QMP6988Oversampling = qmp6988_ns.enum("QMP6988Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": QMP6988Oversampling.QMP6988_OVERSAMPLING_SKIPPED,
"1X": QMP6988Oversampling.QMP6988_OVERSAMPLING_1X,
"2X": QMP6988Oversampling.QMP6988_OVERSAMPLING_2X,
"4X": QMP6988Oversampling.QMP6988_OVERSAMPLING_4X,
"8X": QMP6988Oversampling.QMP6988_OVERSAMPLING_8X,
"16X": QMP6988Oversampling.QMP6988_OVERSAMPLING_16X,
"32X": QMP6988Oversampling.QMP6988_OVERSAMPLING_32X,
"64X": QMP6988Oversampling.QMP6988_OVERSAMPLING_64X,
}
QMP6988IIRFilter = qmp6988_ns.enum("QMP6988IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": QMP6988IIRFilter.QMP6988_IIR_FILTER_OFF,
"2X": QMP6988IIRFilter.QMP6988_IIR_FILTER_2X,
"4X": QMP6988IIRFilter.QMP6988_IIR_FILTER_4X,
"8X": QMP6988IIRFilter.QMP6988_IIR_FILTER_8X,
"16X": QMP6988IIRFilter.QMP6988_IIR_FILTER_16X,
"32X": QMP6988IIRFilter.QMP6988_IIR_FILTER_32X,
}
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(QMP6988Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
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,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x70))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View File

@@ -1,3 +1,10 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/rc522/rc522.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
/**
* Library based on https://github.com/miguelbalboa/rfid
* and adapted to ESPHome by @glmnet
@@ -6,14 +13,6 @@
*
*
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/rc522/rc522.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace rc522_spi {
class RC522Spi : public rc522::RC522,

View File

@@ -40,6 +40,24 @@ namespace remote_base {
static const char *const TAG = "remote.pronto";
bool ProntoData::operator==(const ProntoData &rhs) const {
std::vector<uint16_t> data1 = encode_pronto(data);
std::vector<uint16_t> data2 = encode_pronto(rhs.data);
uint32_t total_diff = 0;
// Don't need to check the last one, it's the large gap at the end.
for (std::vector<uint16_t>::size_type i = 0; i < data1.size() - 1; ++i) {
int diff = data2[i] - data1[i];
diff *= diff;
if (diff > 9)
return false;
total_diff += diff;
}
return total_diff <= data1.size() * 3;
}
// DO NOT EXPORT from this file
static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU;
static const uint16_t LEARNED_TOKEN = 0x0000U;
@@ -52,6 +70,7 @@ static const uint32_t REFERENCE_FREQUENCY = 4145146UL;
static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0;
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
static const uint16_t MARK_EXCESS_MICROS = 20;
static uint16_t to_frequency_k_hz(uint16_t code) {
if (code == 0)
@@ -107,7 +126,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector<uin
}
}
void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) {
std::vector<uint16_t> encode_pronto(const std::string &str) {
size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1;
std::vector<uint16_t> data;
const char *p = str.c_str();
@@ -122,12 +141,90 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st
data.push_back(x); // If input is conforming, there can be no overflow!
p = *endptr;
}
return data;
}
void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) {
std::vector<uint16_t> data = encode_pronto(str);
send_pronto_(dst, data);
}
void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); }
optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) { return {}; }
uint16_t ProntoProtocol::effective_frequency_(uint16_t frequency) {
return frequency > 0 ? frequency : FALLBACK_FREQUENCY;
}
uint16_t ProntoProtocol::to_timebase_(uint16_t frequency) {
return MICROSECONDS_IN_SECONDS / effective_frequency_(frequency);
}
uint16_t ProntoProtocol::to_frequency_code_(uint16_t frequency) {
return REFERENCE_FREQUENCY / effective_frequency_(frequency);
}
std::string ProntoProtocol::dump_digit_(uint8_t x) {
return std::string(1, (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10))));
}
std::string ProntoProtocol::dump_number_(uint16_t number, bool end /* = false */) {
std::string num;
for (uint8_t i = 0; i < DIGITS_IN_PRONTO_NUMBER; ++i) {
uint8_t shifts = BITS_IN_HEXADECIMAL * (DIGITS_IN_PRONTO_NUMBER - 1 - i);
num += dump_digit_((number >> shifts) & HEX_MASK);
}
if (!end)
num += ' ';
return num;
}
std::string ProntoProtocol::dump_duration_(uint32_t duration, uint16_t timebase, bool end /* = false */) {
return dump_number_((duration + timebase / 2) / timebase, end);
}
std::string ProntoProtocol::compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase) {
std::string out;
for (std::vector<int32_t>::size_type i = 0; i < data->size() - 1; i++) {
int32_t t_length = data->at(i);
uint32_t t_duration;
if (t_length > 0) {
// Mark
t_duration = t_length - MARK_EXCESS_MICROS;
} else {
t_duration = -t_length + MARK_EXCESS_MICROS;
}
out += dump_duration_(t_duration, timebase);
}
// append minimum gap
out += dump_duration_(PRONTO_DEFAULT_GAP, timebase, true);
return out;
}
optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
ProntoData out;
uint16_t frequency = 38000U;
std::vector<int32_t> *data = src.get_raw_data();
std::string prontodata;
prontodata += dump_number_(frequency > 0 ? LEARNED_TOKEN : LEARNED_NON_MODULATED_TOKEN);
prontodata += dump_number_(to_frequency_code_(frequency));
prontodata += dump_number_((data->size() + 1) / 2);
prontodata += dump_number_(0);
uint16_t timebase = to_timebase_(frequency);
prontodata += compensate_and_dump_sequence_(data, timebase);
out.data = prontodata;
return out;
}
void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); }

View File

@@ -6,10 +6,12 @@
namespace esphome {
namespace remote_base {
std::vector<uint16_t> encode_pronto(const std::string &str);
struct ProntoData {
std::string data;
bool operator==(const ProntoData &rhs) const { return data == rhs.data; }
bool operator==(const ProntoData &rhs) const;
};
class ProntoProtocol : public RemoteProtocol<ProntoData> {
@@ -17,6 +19,14 @@ class ProntoProtocol : public RemoteProtocol<ProntoData> {
void send_pronto_(RemoteTransmitData *dst, const std::vector<uint16_t> &data);
void send_pronto_(RemoteTransmitData *dst, const std::string &str);
uint16_t effective_frequency_(uint16_t frequency);
uint16_t to_timebase_(uint16_t frequency);
uint16_t to_frequency_code_(uint16_t frequency);
std::string dump_digit_(uint8_t x);
std::string dump_number_(uint16_t number, bool end = false);
std::string dump_duration_(uint32_t duration, uint16_t timebase, bool end = false);
std::string compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase);
public:
void encode(RemoteTransmitData *dst, const ProntoData &data) override;
optional<ProntoData> decode(RemoteReceiveData src) override;

View File

@@ -33,14 +33,8 @@ void SCD30Component::setup() {
#endif
/// Firmware version identification
if (!this->write_command_(SCD30_CMD_GET_FIRMWARE_VERSION)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
uint16_t raw_firmware_version[3];
if (!this->read_data_(raw_firmware_version, 3)) {
if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
this->mark_failed();
return;
@@ -49,7 +43,7 @@ void SCD30Component::setup() {
uint16_t(raw_firmware_version[0] & 0xFF));
if (this->temperature_offset_ != 0) {
if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -69,7 +63,7 @@ void SCD30Component::setup() {
delay(30);
#endif
if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -81,7 +75,7 @@ void SCD30Component::setup() {
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
if (this->altitude_compensation_ != 0xFFFF) {
if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -92,7 +86,7 @@ void SCD30Component::setup() {
delay(30);
#endif
if (!this->write_command_(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -103,7 +97,7 @@ void SCD30Component::setup() {
#endif
/// Sensor initialization
if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -151,14 +145,14 @@ void SCD30Component::dump_config() {
}
void SCD30Component::update() {
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
uint16_t raw_read_status;
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
this->status_set_warning();
ESP_LOGW(TAG, "Data not ready yet!");
return;
}
if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement!");
this->status_set_warning();
return;
@@ -166,7 +160,7 @@ void SCD30Component::update() {
this->set_timeout(50, [this]() {
uint16_t raw_data[6];
if (!this->read_data_(raw_data, 6)) {
if (!this->read_data(raw_data, 6)) {
this->status_set_warning();
return;
}
@@ -197,77 +191,16 @@ void SCD30Component::update() {
}
bool SCD30Component::is_data_ready_() {
if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) {
return false;
}
delay(4);
uint16_t is_data_ready;
if (!this->read_data_(&is_data_ready, 1)) {
if (!this->read_data(&is_data_ready, 1)) {
return false;
}
return is_data_ready == 1;
}
bool SCD30Component::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
bool SCD30Component::write_command_(uint16_t command, uint16_t data) {
uint8_t raw[5];
raw[0] = command >> 8;
raw[1] = command & 0xFF;
raw[2] = data >> 8;
raw[3] = data & 0xFF;
raw[4] = sht_crc_(raw[2], raw[3]);
return this->write(raw, 5) == i2c::ERROR_OK;
}
uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SCD30Component::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace scd30
} // namespace esphome

View File

@@ -2,13 +2,13 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace scd30 {
/// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors.
class SCD30Component : public Component, public i2c::I2CDevice {
class SCD30Component : public Component, public sensirion_common::SensirionI2CDevice {
public:
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
@@ -27,10 +27,6 @@ class SCD30Component : public Component, public i2c::I2CDevice {
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
bool write_command_(uint16_t command);
bool write_command_(uint16_t command, uint16_t data);
bool read_data_(uint16_t *data, uint8_t len);
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
bool is_data_ready_();
enum ErrorCode {

View File

@@ -2,6 +2,7 @@ from esphome import core
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
CONF_ID,
CONF_HUMIDITY,
@@ -18,9 +19,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
scd30_ns = cg.esphome_ns.namespace("scd30")
SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice)
SCD30Component = scd30_ns.class_(
"SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice
)
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"

View File

@@ -25,15 +25,8 @@ void SCD4XComponent::setup() {
// the sensor needs 1000 ms to enter the idle state
this->set_timeout(1000, [this]() {
// Check if measurement is ready before reading the value
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
ESP_LOGE(TAG, "Failed to write data ready status command");
this->mark_failed();
return;
}
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1)) {
uint16_t raw_read_status;
if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) {
ESP_LOGE(TAG, "Failed to read data ready status");
this->mark_failed();
return;
@@ -41,9 +34,9 @@ void SCD4XComponent::setup() {
uint32_t stop_measurement_delay = 0;
// In order to query the device periodic measurement must be ceased
if (raw_read_status[0]) {
if (raw_read_status) {
ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement");
if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) {
if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
ESP_LOGE(TAG, "Failed to stop measurements");
this->mark_failed();
return;
@@ -53,15 +46,8 @@ void SCD4XComponent::setup() {
stop_measurement_delay = 500;
}
this->set_timeout(stop_measurement_delay, [this]() {
if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) {
ESP_LOGE(TAG, "Failed to write get serial command");
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
uint16_t raw_serial_number[3];
if (!this->read_data_(raw_serial_number, 3)) {
if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) {
ESP_LOGE(TAG, "Failed to read serial number");
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
this->mark_failed();
@@ -70,8 +56,8 @@ void SCD4XComponent::setup() {
ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8),
uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8));
if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET,
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET,
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
ESP_LOGE(TAG, "Error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -88,7 +74,7 @@ void SCD4XComponent::setup() {
return;
}
} else {
if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
ESP_LOGE(TAG, "Error setting altitude compensation.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -96,7 +82,7 @@ void SCD4XComponent::setup() {
}
}
if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
ESP_LOGE(TAG, "Error setting automatic self calibration.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -104,7 +90,7 @@ void SCD4XComponent::setup() {
}
// Finally start sensor measurements
if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
if (!this->write_command(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
ESP_LOGE(TAG, "Error starting continuous measurements.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -164,19 +150,19 @@ void SCD4XComponent::update() {
}
// Check if data is ready
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) {
this->status_set_warning();
return;
}
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
uint16_t raw_read_status;
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
this->status_set_warning();
ESP_LOGW(TAG, "Data not ready yet!");
return;
}
if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement!");
this->status_set_warning();
return;
@@ -184,7 +170,7 @@ void SCD4XComponent::update() {
// Read off sensor data
uint16_t raw_data[3];
if (!this->read_data_(raw_data, 3)) {
if (!this->read_data(raw_data, 3)) {
this->status_set_warning();
return;
}
@@ -218,7 +204,7 @@ void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) {
}
bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) {
if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
if (this->write_command(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa);
return true;
} else {
@@ -227,70 +213,5 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_
}
}
uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
bool SCD4XComponent::write_command_(uint16_t command) {
const uint8_t num_bytes = 2;
uint8_t buffer[num_bytes];
buffer[0] = (command >> 8);
buffer[1] = command & 0xff;
return this->write(buffer, num_bytes) == i2c::ERROR_OK;
}
bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) {
uint8_t raw[5];
raw[0] = command >> 8;
raw[1] = command & 0xFF;
raw[2] = data >> 8;
raw[3] = data & 0xFF;
raw[4] = sht_crc_(raw[2], raw[3]);
return this->write(raw, 5) == i2c::ERROR_OK;
}
} // namespace scd4x
} // namespace esphome

View File

@@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace scd4x {
enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN };
class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;
@@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
protected:
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
bool read_data_(uint16_t *data, uint8_t len);
bool write_command_(uint16_t command);
bool write_command_(uint16_t command, uint16_t data);
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa);
ERRORCODE error_code_;

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
CONF_ID,
CONF_CO2,
@@ -21,9 +21,12 @@ from esphome.const import (
CODEOWNERS = ["@sjtrny"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
scd4x_ns = cg.esphome_ns.namespace("scd4x")
SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice)
SCD4XComponent = scd4x_ns.class_(
"SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"

View File

@@ -7,55 +7,50 @@ namespace esphome {
namespace sdp3x {
static const char *const TAG = "sdp3x.sensor";
static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
static const uint16_t SDP3X_SOFT_RESET = 0x0006;
static const uint16_t SDP3X_READ_ID1 = 0x367C;
static const uint16_t SDP3X_READ_ID2 = 0xE102;
static const uint16_t SDP3X_START_DP_AVG = 0x3615;
static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603;
static const uint16_t SDP3X_STOP_MEAS = 0x3FF9;
void SDP3XComponent::update() { this->read_pressure_(); }
void SDP3XComponent::setup() {
ESP_LOGD(TAG, "Setting up SDP3X...");
if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) {
if (!this->write_command(SDP3X_STOP_MEAS)) {
ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason
}
if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) {
if (!this->write_command(SDP3X_SOFT_RESET)) {
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
}
this->set_timeout(20, [this] {
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
if (!this->write_command(SDP3X_READ_ID1)) {
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
this->mark_failed();
return;
}
if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
if (!this->write_command(SDP3X_READ_ID2)) {
ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
this->mark_failed();
return;
}
uint8_t data[18];
if (this->read(data, 18) != i2c::ERROR_OK) {
uint16_t data[6];
if (this->read_data(data, 6) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID SDP3X failed!");
this->mark_failed();
return;
}
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
ESP_LOGE(TAG, "CRC ID SDP3X failed!");
this->mark_failed();
return;
}
// SDP8xx
// ref:
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
if (data[2] == 0x02) {
switch (data[3]) {
if (data[1] >> 8 == 0x02) {
switch (data[1] & 0xFF) {
case 0x01: // SDP800-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
break;
@@ -75,15 +70,16 @@ void SDP3XComponent::setup() {
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
break;
}
} else if (data[2] == 0x01) {
if (data[3] == 0x01) {
} else if (data[1] >> 8 == 0x01) {
if ((data[1] & 0xFF) == 0x01) {
ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
} else if (data[3] == 0x02) {
} else if ((data[1] & 0xFF) == 0x02) {
ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
}
}
if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
if (this->write_command(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG) !=
i2c::ERROR_OK) {
ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
this->mark_failed();
return;
@@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() {
}
void SDP3XComponent::read_pressure_() {
uint8_t data[9];
if (this->read(data, 9) != i2c::ERROR_OK) {
uint16_t data[3];
if (this->read_data(data, 3) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Couldn't read SDP3X data!");
this->status_set_warning();
return;
}
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) {
ESP_LOGW(TAG, "Invalid SDP3X data!");
this->status_set_warning();
return;
}
int16_t pressure_raw = encode_uint16(data[0], data[1]);
int16_t temperature_raw = encode_uint16(data[3], data[4]);
int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
int16_t pressure_raw = data[0];
int16_t temperature_raw = data[1];
int16_t scale_factor_raw = data[2];
// scale factor is in Pa - convert to hPa
float pressure = pressure_raw / (scale_factor_raw * 100.0f);
ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
@@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() {
float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; }
// Check CRC function from SDP3X sample code provided by sensirion
// Returns true if a checksum is OK
bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) {
uint8_t crc = 0xFF;
// calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1)
for (int i = 0; i < size; i++) {
crc ^= (data[i]);
for (uint8_t bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x31;
} else {
crc = (crc << 1);
}
}
}
// verify checksum
return (crc == checksum);
}
} // namespace sdp3x
} // namespace esphome

View File

@@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sdp3x {
enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
class SDP3XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice, public sensor::Sensor {
public:
/// Schedule temperature+pressure readings.
void update() override;
@@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
protected:
/// Internal method to read the pressure from the component after it has been scheduled.
void read_pressure_();
bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
MeasurementMode measurement_mode_;
};

View File

@@ -1,6 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
@@ -8,10 +9,13 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
CODEOWNERS = ["@Azimath"]
sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
SDP3XComponent = sdp3x_ns.class_(
"SDP3XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
MeasurementMode = sdp3x_ns.enum("MeasurementMode")

View File

@@ -0,0 +1,10 @@
import esphome.codegen as cg
from esphome.components import i2c
CODEOWNERS = ["@martgras"]
sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common")
SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice)

View File

@@ -0,0 +1,128 @@
#include "i2c_sensirion.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome {
namespace sensirion_common {
static const char *const TAG = "sensirion_i2c";
// To avoid memory allocations for small writes a stack buffer is used
static const size_t BUFFER_STACK_SIZE = 16;
bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
last_error_ = this->read(buf.data(), num_bytes);
if (last_error_ != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc);
last_error_ = i2c::ERROR_CRC;
return false;
}
data[i] = encode_uint16(buf[j], buf[j + 1]);
}
return true;
}
/***
* write command with parameters and insert crc
* use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters
*/
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
uint8_t data_len) {
uint8_t temp_stack[BUFFER_STACK_SIZE];
std::unique_ptr<uint8_t[]> temp_heap;
uint8_t *temp;
size_t required_buffer_len = data_len * 3 + 2;
// Is a dynamic allocation required ?
if (required_buffer_len >= BUFFER_STACK_SIZE) {
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
temp = temp_heap.get();
} else {
temp = temp_stack;
}
// First byte or word is the command
uint8_t raw_idx = 0;
if (command_len == 1) {
temp[raw_idx++] = command & 0xFF;
} else {
// command is 2 bytes
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = command >> 8;
temp[raw_idx++] = command & 0xFF;
#else
temp[raw_idx++] = command & 0xFF;
temp[raw_idx++] = command >> 8;
#endif
}
// add parameters folllowed by crc
// skipped if len == 0
for (size_t i = 0; i < data_len; i++) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = data[i] >> 8;
temp[raw_idx++] = data[i] & 0xFF;
#else
temp[raw_idx++] = data[i] & 0xFF;
temp[raw_idx++] = data[i] >> 8;
#endif
temp[raw_idx++] = sht_crc_(data[i]);
}
last_error_ = this->write(temp, raw_idx);
return last_error_ == i2c::ERROR_OK;
}
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len,
uint8_t delay_ms) {
if (!this->write_command_(reg, command_len, nullptr, 0)) {
ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
return false;
}
delay(delay_ms);
bool result = this->read_data(data, len);
if (!result) {
ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_);
}
return result;
}
// The 8-bit CRC checksum is transmitted after each data word
uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) {
uint8_t bit;
uint8_t crc = 0xFF;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data >> 8;
#else
crc ^= data & 0xFF;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data & 0xFF;
#else
crc ^= data >> 8;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
return crc;
}
} // namespace sensirion_common
} // namespace esphome

View File

@@ -0,0 +1,155 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace sensirion_common {
/**
* Implementation of a i2c functions for Sensirion sensors
* Sensirion data requires crc checking.
* Each 16 bit word is/must be followed 8 bit CRC code
* (Applies to read and write - note the i2c command code doesn't need a CRC)
* Format:
* | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | ..
*/
class SensirionI2CDevice : public i2c::I2CDevice {
public:
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
/** Read data words from i2c device.
* handles crc check used by Sensirion sensors
* @param data pointer to raw result
* @param len number of words to read
* @return true if reading succeded
*/
bool read_data(uint16_t *data, uint8_t len);
/** Read 1 data word from i2c device.
* @param data reference to raw result
* @return true if reading succeded
*/
bool read_data(uint16_t &data) { return this->read_data(&data, 1); }
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(command, ADDR_16_BIT, data, len, delay);
}
/** Read 1 data word from 16 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay);
}
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(i2c_register, ADDR_8_BIT, data, len, delay);
}
/** Read 1 data word from 8 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay);
}
/** Write a command to the i2c device.
* @param command i2c command to send
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); }
/** Write a command and one data word to the i2c device .
* @param command i2c command to send
* @param data argument for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); }
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data vector<uint16> arguments for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const std::vector<uint16_t> &data) {
return write_command_(i2c_register, sizeof(T), data.data(), data.size());
}
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data arguments for the i2c command
* @param len number of arguments (words)
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const uint16_t *data, uint8_t len) {
// limit to 8 or 16 bit only
static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2,
"only 8 or 16 bit command types are supported.");
return write_command_(i2c_register, CommandLen(sizeof(T)), data, len);
}
protected:
uint8_t crc_polynomial_{0x31u}; // default for sensirion
/** Write a command with arguments as words
* @param command i2c command to send can be uint8_t or uint16_t
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data arguments for the i2c command
* @param data_len number of arguments (words)
* @return true if reading succeded
*/
bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len);
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data data word for which the crc8 checksum is calculated
* @param len number of arguments (words)
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint16_t data);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data1 high byte of data word
* @param data2 low byte of data word
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); }
/** last error code from i2c operation
*/
i2c::ErrorCode last_error_;
};
} // namespace sensirion_common
} // namespace esphome

View File

@@ -212,8 +212,8 @@ SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.float_,
cv.Optional(CONF_BELOW): cv.float_,
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),

View File

@@ -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, sensirion_common
from esphome.const import (
CONF_ID,
CONF_BASELINE,
CONF_ECO2,
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
CONF_TVOC,
ICON_RADIATOR,
DEVICE_CLASS_CARBON_DIOXIDE,
@@ -13,20 +16,23 @@ from esphome.const import (
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
ICON_MOLECULE_CO2,
ENTITY_CATEGORY_DIAGNOSTIC,
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sgp30_ns = cg.esphome_ns.namespace("sgp30")
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
SGP30Component = sgp30_ns.class_(
"SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONF_ECO2_BASELINE = "eco2_baseline"
CONF_TVOC_BASELINE = "tvoc_baseline"
CONF_STORE_BASELINE = "store_baseline"
CONF_UPTIME = "uptime"
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONFIG_SCHEMA = (
cv.Schema(
@@ -49,10 +55,12 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema(
icon=ICON_MOLECULE_CO2,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema(
icon=ICON_RADIATOR,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
cv.Optional(CONF_BASELINE): cv.Schema(

View File

@@ -36,14 +36,8 @@ void SGP30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
// Serial Number identification
if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
uint16_t raw_serial_number[3];
if (!this->read_data_(raw_serial_number, 3)) {
if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) {
this->mark_failed();
return;
}
@@ -52,16 +46,12 @@ void SGP30Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// Featureset identification for future use
if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) {
uint16_t raw_featureset;
if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) {
this->mark_failed();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if (uint16_t(this->featureset_ >> 12) != 0x0) {
if (uint16_t(this->featureset_ >> 12) == 0x1) {
// ID matching a different sensor: SGPC3
@@ -76,7 +66,7 @@ void SGP30Component::setup() {
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
// Sensor initialization
if (!this->write_command_(SGP30_CMD_IAQ_INIT)) {
if (!this->write_command(SGP30_CMD_IAQ_INIT)) {
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() {
void SGP30Component::read_iaq_baseline_() {
if (this->is_sensor_baseline_reliable_()) {
if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) {
if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) {
ESP_LOGD(TAG, "Error getting baseline");
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@@ -274,14 +264,14 @@ void SGP30Component::dump_config() {
}
void SGP30Component::update() {
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) {
this->status_set_warning();
return;
}
this->seconds_since_last_store_ += this->update_interval_ / 1000;
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@@ -305,56 +295,5 @@ void SGP30Component::update() {
});
}
bool SGP30Component::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SGP30Component::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace sgp30
} // namespace esphome

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
#include "esphome/core/preferences.h"
#include <cmath>
@@ -15,7 +15,7 @@ struct SGP30Baselines {
} PACKED;
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
@@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
void send_env_data_();
void read_iaq_baseline_();
bool is_sensor_baseline_reliable_();
void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_;
uint16_t featureset_;
uint32_t required_warm_up_time_;

View File

@@ -1,25 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
ICON_RADIATOR,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
CODEOWNERS = ["@SenexCrenshaw"]
sgp40_ns = cg.esphome_ns.namespace("sgp40")
SGP40Component = sgp40_ns.class_(
"SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
"SGP40Component",
sensor.Sensor,
cg.PollingComponent,
sensirion_common.SensirionI2CDevice,
)
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONF_STORE_BASELINE = "store_baseline"
CONF_VOC_BASELINE = "voc_baseline"
CONFIG_SCHEMA = (

View File

@@ -12,14 +12,14 @@ void SGP40Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
// Serial Number identification
if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) {
if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
uint16_t raw_serial_number[3];
if (!this->read_data_(raw_serial_number, 3)) {
if (!this->read_data(raw_serial_number, 3)) {
this->mark_failed();
return;
}
@@ -28,19 +28,19 @@ void SGP40Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// Featureset identification for future use
if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) {
if (!this->write_command(SGP40_CMD_GET_FEATURESET)) {
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
this->mark_failed();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
uint16_t raw_featureset;
if (!this->read_data(raw_featureset)) {
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
SGP40_FEATURESET);
@@ -95,21 +95,21 @@ void SGP40Component::setup() {
void SGP40Component::self_test_() {
ESP_LOGD(TAG, "Self-test started");
if (!this->write_command_(SGP40_CMD_SELF_TEST)) {
if (!this->write_command(SGP40_CMD_SELF_TEST)) {
this->error_code_ = COMMUNICATION_FAILED;
ESP_LOGD(TAG, "Self-test communication failed");
this->mark_failed();
}
this->set_timeout(250, [this]() {
uint16_t reply[1];
if (!this->read_data_(reply, 1)) {
uint16_t reply;
if (!this->read_data(reply)) {
ESP_LOGD(TAG, "Self-test read_data_ failed");
this->mark_failed();
return;
}
if (reply[0] == 0xD400) {
if (reply == 0xD400) {
this->self_test_complete_ = true;
ESP_LOGD(TAG, "Self-test completed");
return;
@@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() {
temperature = 25;
}
uint8_t command[8];
command[0] = 0x26;
command[1] = 0x0F;
uint16_t data[2];
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
command[2] = rhticks >> 8;
command[3] = rhticks & 0xFF;
command[4] = generate_crc_(command + 2, 2);
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
command[5] = tempticks >> 8;
command[6] = tempticks & 0xFF;
command[7] = generate_crc_(command + 5, 2);
// first paramater is the relative humidity ticks
data[0] = rhticks;
// second paramater is the temperature ticks
data[1] = tempticks;
if (this->write(command, 8) != i2c::ERROR_OK) {
if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) {
this->status_set_warning();
ESP_LOGD(TAG, "write error");
return UINT16_MAX;
ESP_LOGD(TAG, "write error (%d)", this->last_error_);
return false;
}
delay(30);
uint16_t raw_data[1];
if (!this->read_data_(raw_data, 1)) {
uint16_t raw_data;
if (!this->read_data(raw_data)) {
this->status_set_warning();
ESP_LOGD(TAG, "read_data_ error");
return UINT16_MAX;
}
return raw_data[0];
}
uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) {
// calculates 8-Bit checksum with given polynomial
uint8_t crc = SGP40_CRC8_INIT;
for (uint8_t i = 0; i < datalen; i++) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; b++) {
if (crc & 0x80) {
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
} else {
crc <<= 1;
}
}
}
return crc;
return raw_data;
}
void SGP40Component::update_voc_index() {
@@ -293,56 +270,5 @@ void SGP40Component::dump_config() {
}
}
bool SGP40Component::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SGP40Component::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace sgp40
} // namespace esphome

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
#include "esphome/core/application.h"
#include "esphome/core/preferences.h"
#include "sensirion_voc_algorithm.h"
@@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word
static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682;
static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f;
static const uint16_t SGP40_CMD_SELF_TEST = 0x280e;
static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F;
// Shortest time interval of 3H for storing baseline values.
// Prevents wear of the flash because of too many write operations
@@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50;
class SGP40Component;
/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors.
class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice {
class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice {
public:
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
@@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2
/// Input sensor for humidity and temperature compensation.
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
int16_t sensirion_init_sensors_();
int16_t sgp40_probe_();
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_;
uint16_t featureset_;
int32_t measure_voc_index_();

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
@@ -13,10 +13,11 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sht3xd_ns = cg.esphome_ns.namespace("sht3xd")
SHT3XDComponent = sht3xd_ns.class_(
"SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice
"SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONFIG_SCHEMA = (

View File

@@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000;
void SHT3XDComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up SHT3xD...");
if (!this->write_command_(SHT3XD_COMMAND_READ_SERIAL_NUMBER)) {
this->mark_failed();
return;
}
uint16_t raw_serial_number[2];
if (!this->read_data_(raw_serial_number, 2)) {
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
this->mark_failed();
return;
}
@@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA;
void SHT3XDComponent::update() {
if (this->status_has_warning()) {
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
this->write_command_(SHT3XD_COMMAND_SOFT_RESET);
this->write_command(SHT3XD_COMMAND_SOFT_RESET);
}
if (!this->write_command_(SHT3XD_COMMAND_POLLING_H)) {
if (!this->write_command(SHT3XD_COMMAND_POLLING_H)) {
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@@ -71,56 +66,5 @@ void SHT3XDComponent::update() {
});
}
bool SHT3XDComponent::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace sht3xd
} // namespace esphome

View File

@@ -2,13 +2,13 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sht3xd {
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
class SHT3XDComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
@@ -19,9 +19,6 @@ class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
void update() override;
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
sensor::Sensor *temperature_sensor_;
sensor::Sensor *humidity_sensor_;
};

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_TEMPERATURE,
@@ -16,10 +16,13 @@ from esphome.const import (
CODEOWNERS = ["@sjtrny"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sht4x_ns = cg.esphome_ns.namespace("sht4x")
SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice)
SHT4XComponent = sht4x_ns.class_(
"SHT4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONF_PRECISION = "precision"
SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION")

View File

@@ -50,31 +50,28 @@ void SHT4XComponent::setup() {
void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); }
void SHT4XComponent::update() {
uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]};
// Send command
this->write(cmd, 1);
this->write_command(MEASURECOMMANDS[this->precision_]);
this->set_timeout(10, [this]() {
const uint8_t num_bytes = 6;
uint8_t buffer[num_bytes];
uint16_t buffer[2];
// Read measurement
bool read_status = this->read_bytes_raw(buffer, num_bytes);
bool read_status = this->read_data(buffer, 2);
if (read_status) {
// Evaluate and publish measurements
if (this->temp_sensor_ != nullptr) {
// Temp is contained in the first 16 bits
float sensor_value_temp = (buffer[0] << 8) + buffer[1];
// Temp is contained in the first result word
float sensor_value_temp = buffer[0];
float temp = -45 + 175 * sensor_value_temp / 65535;
this->temp_sensor_->publish_state(temp);
}
if (this->humidity_sensor_ != nullptr) {
// Relative humidity is in the last 16 bits
float sensor_value_rh = (buffer[3] << 8) + buffer[4];
// Relative humidity is in the second result word
float sensor_value_rh = buffer[1];
float rh = -6 + 125 * sensor_value_rh / 65535;
this->humidity_sensor_->publish_state(rh);

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sht4x {
@@ -13,7 +13,7 @@ enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEA
enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 };
class SHT4XComponent : public PollingComponent, public i2c::I2CDevice {
class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
@@ -13,9 +13,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
shtcx_ns = cg.esphome_ns.namespace("shtcx")
SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice)
SHTCXComponent = shtcx_ns.class_(
"SHTCXComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
SHTCXType = shtcx_ns.enum("SHTCXType")

View File

@@ -29,21 +29,23 @@ void SHTCXComponent::setup() {
this->wake_up();
this->soft_reset();
if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) {
if (!this->write_command(SHTCX_COMMAND_READ_ID_REGISTER)) {
ESP_LOGE(TAG, "Error requesting Device ID");
this->mark_failed();
return;
}
uint16_t device_id_register[1];
if (!this->read_data_(device_id_register, 1)) {
uint16_t device_id_register;
if (!this->read_data(&device_id_register, 1)) {
ESP_LOGE(TAG, "Error reading Device ID");
this->mark_failed();
return;
}
if (((device_id_register[0] << 2) & 0x1C) == 0x1C) {
if ((device_id_register[0] & 0x847) == 0x847) {
this->sensor_id_ = device_id_register;
if ((device_id_register & 0x3F) == 0x07) {
if (device_id_register & 0x800) {
this->type_ = SHTCX_TYPE_SHTC3;
} else {
this->type_ = SHTCX_TYPE_SHTC1;
@@ -51,11 +53,11 @@ void SHTCXComponent::setup() {
} else {
this->type_ = SHTCX_TYPE_UNKNOWN;
}
ESP_LOGCONFIG(TAG, " Device identified: %s", to_string(this->type_));
ESP_LOGCONFIG(TAG, " Device identified: %s (%04x)", to_string(this->type_), device_id_register);
}
void SHTCXComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SHTCx:");
ESP_LOGCONFIG(TAG, " Model: %s", to_string(this->type_));
ESP_LOGCONFIG(TAG, " Model: %s (%04x)", to_string(this->type_), this->sensor_id_);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with SHTCx failed!");
@@ -74,22 +76,29 @@ void SHTCXComponent::update() {
if (this->type_ != SHTCX_TYPE_SHTC1) {
this->wake_up();
}
if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) {
if (!this->write_command(SHTCX_COMMAND_POLLING_H)) {
ESP_LOGE(TAG, "sensor polling failed");
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(NAN);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(NAN);
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
float temperature = NAN;
float humidity = NAN;
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
ESP_LOGE(TAG, "sensor read failed");
this->status_set_warning();
return;
} else {
temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
humidity = 100.0f * float(raw_data[1]) / 65536.0f;
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
}
float temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
float humidity = 100.0f * float(raw_data[1]) / 65536.0f;
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
@@ -101,65 +110,14 @@ void SHTCXComponent::update() {
});
}
bool SHTCXComponent::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
void SHTCXComponent::soft_reset() {
this->write_command_(SHTCX_COMMAND_SOFT_RESET);
this->write_command(SHTCX_COMMAND_SOFT_RESET);
delayMicroseconds(200);
}
void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); }
void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); }
void SHTCXComponent::wake_up() {
this->write_command_(SHTCX_COMMAND_WAKEUP);
this->write_command(SHTCX_COMMAND_WAKEUP);
delayMicroseconds(200);
}

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace shtcx {
@@ -10,7 +10,7 @@ namespace shtcx {
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
@@ -24,9 +24,8 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
void wake_up();
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
SHTCXType type_;
uint16_t sensor_id_;
sensor::Sensor *temperature_sensor_;
sensor::Sensor *humidity_sensor_;
};

View File

@@ -13,7 +13,6 @@ CONFIG_SCHEMA = cv.Schema(
CONF_IMPLEMENTATION,
esp8266=IMPLEMENTATION_LWIP_TCP,
esp32=IMPLEMENTATION_BSD_SOCKETS,
host=IMPLEMENTATION_BSD_SOCKETS,
): cv.one_of(
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
),

View File

@@ -119,12 +119,6 @@ struct iovec {
#include <sys/uio.h>
#include <unistd.h>
#ifdef USE_HOST
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#endif // USE_HOST
#ifdef USE_ARDUINO
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
// by the define

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@anatoly-savchenkov"]

View File

@@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart, light
from esphome.const import (
CONF_OUTPUT_ID,
CONF_MIN_VALUE,
CONF_MAX_VALUE,
)
CONF_USE_RM433_REMOTE = "use_rm433_remote"
DEPENDENCIES = ["uart", "light"]
sonoff_d1_ns = cg.esphome_ns.namespace("sonoff_d1")
SonoffD1Output = sonoff_d1_ns.class_(
"SonoffD1Output", cg.Component, uart.UARTDevice, light.LightOutput
)
CONFIG_SCHEMA = (
light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SonoffD1Output),
cv.Optional(CONF_USE_RM433_REMOTE, default=False): cv.boolean,
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_range(min=0, max=100),
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_range(min=0, max=100),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"sonoff_d1", baud_rate=9600, require_tx=True, require_rx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add(var.set_use_rm433_remote(config[CONF_USE_RM433_REMOTE]))
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
await light.register_light(var, config)

View File

@@ -0,0 +1,308 @@
/*
sonoff_d1.cpp - Sonoff D1 Dimmer support for ESPHome
Copyright © 2021 Anatoly Savchenkov
Copyright © 2020 Jeff Rescignano
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----
If modifying this file, in addition to the license above, please ensure to include links back to the original code:
https://jeffresc.dev/blog/2020-10-10
https://github.com/JeffResc/Sonoff-D1-Dimmer
https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131
-----
*/
/*********************************************************************************************\
* Sonoff D1 dimmer 433
* Mandatory/Optional
* ^ 0 1 2 3 4 5 6 7 8 9 A B C D E F 10
* M AA 55 - Header
* M 01 04 - Version?
* M 00 0A - Following data length (10 bytes)
* O 01 - Power state (00 = off, 01 = on, FF = ignore)
* O 64 - Dimmer percentage (01 to 64 = 1 to 100%, 0 - ignore)
* O FF FF FF FF FF FF FF FF - Not used
* M 6C - CRC over bytes 2 to F (Addition)
\*********************************************************************************************/
#include <cmath>
#include "sonoff_d1.h"
namespace esphome {
namespace sonoff_d1 {
static const char *const TAG = "sonoff_d1";
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
uint8_t crc = 0;
for (int i = 2; i < len - 1; i++) {
crc += cmd[i];
}
return crc;
}
void SonoffD1Output::populate_checksum_(uint8_t *cmd, const size_t len) {
// Update the checksum
cmd[len - 1] = this->calc_checksum_(cmd, len);
}
void SonoffD1Output::skip_command_() {
size_t garbage = 0;
// Read out everything from the UART FIFO
while (this->available()) {
uint8_t value = this->read();
ESP_LOGW(TAG, "[%04d] Skip %02d: 0x%02x from the dimmer", this->write_count_, garbage, value);
garbage++;
}
// Warn about unexpected bytes in the protocol with UART dimmer
if (garbage)
ESP_LOGW(TAG, "[%04d] Skip %d bytes from the dimmer", this->write_count_, garbage);
}
// This assumes some data is already available
bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
// Do consistency check
if (cmd == nullptr || len < 7) {
ESP_LOGW(TAG, "[%04d] Too short command buffer (actual len is %d bytes, minimal is 7)", this->write_count_, len);
return false;
}
// Read a minimal packet
if (this->read_array(cmd, 6)) {
ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str());
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
this->skip_command_();
return false;
}
if ((cmd[5] + 7 /*mandatory header + crc suffix length*/) > len) {
ESP_LOGW(TAG, "[%04d] RX: Payload length is unexpected (%d, max expected %d)", this->write_count_, cmd[5],
len - 7);
this->skip_command_();
return false;
}
if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) {
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str());
// Check the checksum
uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7);
if (valid_checksum != cmd[cmd[5] + 7 - 1]) {
ESP_LOGW(TAG, "[%04d] RX: checksum mismatch (%d, expected %d)", this->write_count_, cmd[cmd[5] + 7 - 1],
valid_checksum);
this->skip_command_();
return false;
}
len = cmd[5] + 7 /*mandatory header + suffix length*/;
// Read remaining gardbled data (just in case, I don't see where this can appear now)
this->skip_command_();
return true;
}
} else {
ESP_LOGW(TAG, "[%04d] RX: feedback timeout", this->write_count_);
this->skip_command_();
}
return false;
}
bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
// Expected acknowledgement from rf chip
uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
uint8_t buffer[sizeof(ref_buffer)] = {0};
uint32_t pos = 0, buf_len = sizeof(ref_buffer);
// Update the reference checksum
this->populate_checksum_(ref_buffer, sizeof(ref_buffer));
// Read ack code, this either reads 7 bytes or exits with a timeout
this->read_command_(buffer, buf_len);
// Compare response with expected response
while (pos < sizeof(ref_buffer) && ref_buffer[pos] == buffer[pos]) {
pos++;
}
if (pos == sizeof(ref_buffer)) {
ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_);
return true;
} else {
ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:",
this->write_count_);
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str());
}
return false;
}
bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_ack) {
// Do some consistency checks
if (len < 7) {
ESP_LOGW(TAG, "[%04d] Too short command (actual len is %d bytes, minimal is 7)", this->write_count_, len);
return false;
}
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
ESP_LOGW(TAG, "[%04d] Wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
return false;
}
if ((cmd[5] + 7 /*mandatory header + suffix length*/) != len) {
ESP_LOGW(TAG, "[%04d] Payload length field does not match packet lenght (%d, expected %d)", this->write_count_,
cmd[5], len - 7);
return false;
}
this->populate_checksum_(cmd, len);
// Need retries here to handle the following cases:
// 1. On power up companion MCU starts to respond with a delay, so few first commands are ignored
// 2. UART command initiated by this component can clash with a command initiated by RF
uint32_t retries = 10;
do {
ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str());
this->write_array(cmd, len);
this->write_count_++;
if (!needs_ack)
return true;
retries--;
} while (!this->read_ack_(cmd, len) && retries > 0);
if (retries) {
return true;
} else {
ESP_LOGE(TAG, "[%04d] Unable to write to the dimmer", this->write_count_);
}
return false;
}
bool SonoffD1Output::control_dimmer_(const bool binary, const uint8_t brightness) {
// Include our basic code from the Tasmota project, thank you again!
// 0 1 2 3 4 5 6 7 8
uint8_t cmd[17] = {0xAA, 0x55, 0x01, 0x04, 0x00, 0x0A, 0x00, 0x00, 0xFF,
// 9 10 11 12 13 14 15 16
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
cmd[6] = binary;
cmd[7] = remap<uint8_t, uint8_t>(brightness, 0, 100, this->min_value_, this->max_value_);
ESP_LOGI(TAG, "[%04d] Setting dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(binary), cmd[7]);
return this->write_command_(cmd, sizeof(cmd));
}
void SonoffD1Output::process_command_(const uint8_t *cmd, const size_t len) {
if (cmd[2] == 0x01 && cmd[3] == 0x04 && cmd[4] == 0x00 && cmd[5] == 0x0A) {
uint8_t ack_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
// Ack a command from RF to ESP to prevent repeating commands
this->write_command_(ack_buffer, sizeof(ack_buffer), false);
ESP_LOGI(TAG, "[%04d] RF sets dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(cmd[6]), cmd[7]);
const uint8_t new_brightness = remap<uint8_t, uint8_t>(cmd[7], this->min_value_, this->max_value_, 0, 100);
const bool new_state = cmd[6];
// Got light change state command. In all cases we revert the command immediately
// since we want to rely on ESP controlled transitions
if (new_state != this->last_binary_ || new_brightness != this->last_brightness_) {
this->control_dimmer_(this->last_binary_, this->last_brightness_);
}
if (!this->use_rm433_remote_) {
// If RF remote is not used, this is a known ghost RF command
ESP_LOGI(TAG, "[%04d] Ghost command from RF detected, reverted", this->write_count_);
} else {
// If remote is used, initiate transition to the new state
this->publish_state_(new_state, new_brightness);
}
} else {
ESP_LOGW(TAG, "[%04d] Unexpected command received", this->write_count_);
}
}
void SonoffD1Output::publish_state_(const bool is_on, const uint8_t brightness) {
if (light_state_) {
ESP_LOGV(TAG, "Publishing new state: %s, brightness=%d", ONOFF(is_on), brightness);
auto call = light_state_->make_call();
call.set_state(is_on);
if (brightness != 0) {
// Brightness equal to 0 has a special meaning.
// D1 uses 0 as "previously set brightness".
// Usually zero brightness comes inside light ON command triggered by RF remote.
// Since we unconditionally override commands coming from RF remote in process_command_(),
// here we mimic the original behavior but with LightCall functionality
call.set_brightness((float) brightness / 100.0f);
}
call.perform();
}
}
// Set the device's traits
light::LightTraits SonoffD1Output::get_traits() {
auto traits = light::LightTraits();
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
return traits;
}
void SonoffD1Output::write_state(light::LightState *state) {
bool binary;
float brightness;
// Fill our variables with the device's current state
state->current_values_as_binary(&binary);
state->current_values_as_brightness(&brightness);
// Convert ESPHome's brightness (0-1) to the device's internal brightness (0-100)
const uint8_t calculated_brightness = std::round(brightness * 100);
if (calculated_brightness == 0) {
// if(binary) ESP_LOGD(TAG, "current_values_as_binary() returns true for zero brightness");
binary = false;
}
// If a new value, write to the dimmer
if (binary != this->last_binary_ || calculated_brightness != this->last_brightness_) {
if (this->control_dimmer_(binary, calculated_brightness)) {
this->last_brightness_ = calculated_brightness;
this->last_binary_ = binary;
} else {
// Return to original value if failed to write to the dimmer
// TODO: Test me, can be tested if high-voltage part is not connected
ESP_LOGW(TAG, "Failed to update the dimmer, publishing the previous state");
this->publish_state_(this->last_binary_, this->last_brightness_);
}
}
}
void SonoffD1Output::dump_config() {
ESP_LOGCONFIG(TAG, "Sonoff D1 Dimmer: '%s'", this->light_state_ ? this->light_state_->get_name().c_str() : "");
ESP_LOGCONFIG(TAG, " Use RM433 Remote: %s", ONOFF(this->use_rm433_remote_));
ESP_LOGCONFIG(TAG, " Minimal brightness: %d", this->min_value_);
ESP_LOGCONFIG(TAG, " Maximal brightness: %d", this->max_value_);
}
void SonoffD1Output::loop() {
// Read commands from the dimmer
// RF chip notifies ESP about remotely changed state with the same commands as we send
if (this->available()) {
ESP_LOGV(TAG, "Have some UART data in loop()");
uint8_t buffer[17] = {0};
size_t len = sizeof(buffer);
if (this->read_command_(buffer, len)) {
this->process_command_(buffer, len);
}
}
}
} // namespace sonoff_d1
} // namespace esphome

View File

@@ -0,0 +1,85 @@
#pragma once
/*
sonoff_d1.h - Sonoff D1 Dimmer support for ESPHome
Copyright © 2021 Anatoly Savchenkov
Copyright © 2020 Jeff Rescignano
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----
If modifying this file, in addition to the license above, please ensure to include links back to the original code:
https://jeffresc.dev/blog/2020-10-10
https://github.com/JeffResc/Sonoff-D1-Dimmer
https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131
-----
*/
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/light/light_output.h"
#include "esphome/components/light/light_state.h"
#include "esphome/components/light/light_traits.h"
namespace esphome {
namespace sonoff_d1 {
class SonoffD1Output : public light::LightOutput, public uart::UARTDevice, public Component {
public:
// LightOutput methods
light::LightTraits get_traits() override;
void setup_state(light::LightState *state) override { this->light_state_ = state; }
void write_state(light::LightState *state) override;
// Component methods
void setup() override{};
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
// Custom methods
void set_use_rm433_remote(const bool use_rm433_remote) { this->use_rm433_remote_ = use_rm433_remote; }
void set_min_value(const uint8_t min_value) { this->min_value_ = min_value; }
void set_max_value(const uint8_t max_value) { this->max_value_ = max_value; }
protected:
uint8_t min_value_{0};
uint8_t max_value_{100};
bool use_rm433_remote_{false};
bool last_binary_{false};
uint8_t last_brightness_{0};
int write_count_{0};
int read_count_{0};
light::LightState *light_state_{nullptr};
uint8_t calc_checksum_(const uint8_t *cmd, size_t len);
void populate_checksum_(uint8_t *cmd, size_t len);
void skip_command_();
bool read_command_(uint8_t *cmd, size_t &len);
bool read_ack_(const uint8_t *cmd, size_t len);
bool write_command_(uint8_t *cmd, size_t len, bool needs_ack = true);
bool control_dimmer_(bool binary, uint8_t brightness);
void process_command_(const uint8_t *cmd, size_t len);
void publish_state_(bool is_on, uint8_t brightness);
};
} // namespace sonoff_d1
} // namespace esphome

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_PM_1_0,
@@ -26,9 +26,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sps30_ns = cg.esphome_ns.namespace("sps30")
SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice)
SPS30Component = sps30_ns.class_(
"SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(

View File

@@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
void SPS30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up sps30...");
this->write_command_(SPS30_CMD_SOFT_RESET);
this->write_command(SPS30_CMD_SOFT_RESET);
/// Deferred Sensor initialization
this->set_timeout(500, [this]() {
/// Firmware version identification
if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) {
this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED;
this->mark_failed();
return;
}
if (!this->read_data_(&raw_firmware_version_, 1)) {
if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
this->mark_failed();
return;
}
/// Serial number identification
if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) {
this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED;
this->mark_failed();
return;
}
uint16_t raw_serial_number[8];
if (!this->read_data_(raw_serial_number, 8)) {
if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) {
this->error_code_ = SERIAL_NUMBER_READ_FAILED;
this->mark_failed();
return;
@@ -109,7 +97,7 @@ void SPS30Component::update() {
/// Check if warning flag active (sensor reconnected?)
if (this->status_has_warning()) {
ESP_LOGD(TAG, "Trying to reconnect the sensor...");
if (this->write_command_(SPS30_CMD_SOFT_RESET)) {
if (this->write_command(SPS30_CMD_SOFT_RESET)) {
ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
this->set_timeout(500, [this]() {
this->start_continuous_measurement_();
@@ -124,13 +112,13 @@ void SPS30Component::update() {
return;
}
/// Check if measurement is ready before reading the value
if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) {
if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
this->status_set_warning();
return;
}
uint16_t raw_read_status;
if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) {
if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
this->skipped_data_read_cycles_++;
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
@@ -142,7 +130,7 @@ void SPS30Component::update() {
return;
}
if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement status!");
this->status_set_warning();
return;
@@ -150,7 +138,7 @@ void SPS30Component::update() {
this->set_timeout(50, [this]() {
uint16_t raw_data[20];
if (!this->read_data_(raw_data, 20)) {
if (!this->read_data(raw_data, 20)) {
ESP_LOGW(TAG, "Error reading measurement data!");
this->status_set_warning();
return;
@@ -205,69 +193,18 @@ void SPS30Component::update() {
});
}
bool SPS30Component::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool SPS30Component::start_continuous_measurement_() {
uint8_t data[4];
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
data[1] = 0x03;
data[2] = 0x00;
data[3] = sht_crc_(0x03, 0x00);
if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) {
if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
ESP_LOGE(TAG, "Error initiating measurements");
return false;
}
return true;
}
bool SPS30Component::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace sps30
} // namespace esphome

View File

@@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sps30 {
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
/// PM1.0, PM2.5, PM4, PM10 Air Quality sensors.
class SPS30Component : public PollingComponent, public i2c::I2CDevice {
class SPS30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
@@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice {
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
char serial_number_[17] = {0}; /// Terminating NULL character
uint16_t raw_firmware_version_;
bool start_continuous_measurement_();

View File

@@ -8,6 +8,7 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sts3x_ns = cg.esphome_ns.namespace("sts3x")

View File

@@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000;
void STS3XComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up STS3x...");
if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
if (!this->write_command(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
this->mark_failed();
return;
}
uint16_t raw_serial_number[2];
if (!this->read_data_(raw_serial_number, 1)) {
if (!this->read_data(raw_serial_number, 1)) {
this->mark_failed();
return;
}
@@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA;
void STS3XComponent::update() {
if (this->status_has_warning()) {
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
this->write_command_(STS3X_COMMAND_SOFT_RESET);
this->write_command(STS3X_COMMAND_SOFT_RESET);
}
if (!this->write_command_(STS3X_COMMAND_POLLING_H)) {
if (!this->write_command(STS3X_COMMAND_POLLING_H)) {
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[1];
if (!this->read_data_(raw_data, 1)) {
if (!this->read_data(raw_data, 1)) {
this->status_set_warning();
return;
}
@@ -67,56 +67,5 @@ void STS3XComponent::update() {
});
}
bool STS3XComponent::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
}
uint8_t sts3x_crc(uint8_t data1, uint8_t data2) {
uint8_t bit;
uint8_t crc = 0xFF;
crc ^= data1;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
crc ^= data2;
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x131;
} else {
crc = (crc << 1);
}
}
return crc;
}
bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sts3x_crc(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
return false;
}
data[i] = (buf[j] << 8) | buf[j + 1];
}
return true;
}
} // namespace sts3x
} // namespace esphome

View File

@@ -2,22 +2,18 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sts3x {
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
};
} // namespace sts3x

View File

@@ -1,11 +1,15 @@
/******************************************************************************
/*
sx1509_registers.h
Register definitions for SX1509.
Jim Lindblom @ SparkFun Electronics
Original Creation Date: September 21, 2015
https://github.com/sparkfun/SparkFun_SX1509_Arduino_Library
*/
#pragma once
Here you'll find the Arduino code used to interface with the SX1509 I2C
namespace esphome {
/**
Here you'll find the Arduino code used to interface with the SX1509 I2C
16 I/O expander. There are functions to take advantage of everything the
SX1509 provides - input/output setting, writing pins high/low, reading
the input value of pins, LED driver utilities (blink, breath, pwm), and
@@ -20,10 +24,7 @@ This code is beerware; if you see me (or any other SparkFun employee) at the
local, and you've found our code helpful, please buy us a round!
Distributed as-is; no warranty is given.
******************************************************************************/
#pragma once
namespace esphome {
*/
namespace sx1509 {
const uint8_t REG_INPUT_DISABLE_B =

View File

@@ -12,11 +12,11 @@ i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers,
return err;
return parent_->bus_->readv(address, buffers, cnt);
}
i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) {
i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) {
auto err = parent_->switch_to_channel(channel_);
if (err != i2c::ERROR_OK)
return err;
return parent_->bus_->writev(address, buffers, cnt);
return parent_->bus_->writev(address, buffers, cnt, stop);
}
void TCA9548AComponent::setup() {

View File

@@ -13,7 +13,7 @@ class TCA9548AChannel : public i2c::I2CBus {
void set_parent(TCA9548AComponent *parent) { parent_ = parent; }
i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override;
i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) override;
i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) override;
protected:
uint8_t channel_;

View File

@@ -176,6 +176,31 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) {
res += this->second;
this->timestamp = res;
}
int32_t ESPTime::timezone_offset() {
int32_t offset = 0;
time_t now = ::time(nullptr);
auto local = ESPTime::from_epoch_local(now);
auto utc = ESPTime::from_epoch_utc(now);
bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
if (utc.minute > local.minute) {
local.minute += 60;
local.hour -= 1;
}
offset += (local.minute - utc.minute) * 60;
if (negative) {
offset -= (utc.hour - local.hour) * 3600;
} else {
if (utc.hour > local.hour) {
local.hour += 24;
}
offset += (local.hour - utc.hour) * 3600;
}
return offset;
}
bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; }
bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; }
bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; }

View File

@@ -88,6 +88,8 @@ struct ESPTime {
/// Convert this ESPTime instance back to a tm struct.
struct tm to_c_tm();
static int32_t timezone_offset();
/// Increment this clock instance by one second.
void increment_second();
/// Increment this clock instance by one day.

View File

@@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_ID, CONF_KEY
CONF_TM1637_ID = "tm1637_id"
tm1637_ns = cg.esphome_ns.namespace("tm1637")
TM1637Display = tm1637_ns.class_("TM1637Display", cg.PollingComponent)
TM1637Key = tm1637_ns.class_("TM1637Key", binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TM1637Key),
cv.GenerateID(CONF_TM1637_ID): cv.use_id(TM1637Display),
cv.Required(CONF_KEY): cv.int_range(min=0, max=15),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await binary_sensor.register_binary_sensor(var, config)
cg.add(var.set_keycode(config[CONF_KEY]))
hub = await cg.get_variable(config[CONF_TM1637_ID])
cg.add(hub.add_tm1637_key(var))

View File

@@ -7,11 +7,17 @@ namespace esphome {
namespace tm1637 {
static const char *const TAG = "display.tm1637";
const uint8_t TM1637_I2C_COMM1 = 0x40;
const uint8_t TM1637_I2C_COMM2 = 0xC0;
const uint8_t TM1637_I2C_COMM3 = 0x80;
const uint8_t TM1637_CMD_DATA = 0x40; //!< Display data command
const uint8_t TM1637_CMD_CTRL = 0x80; //!< Display control command
const uint8_t TM1637_CMD_ADDR = 0xc0; //!< Display address command
const uint8_t TM1637_UNKNOWN_CHAR = 0b11111111;
// Data command bits
const uint8_t TM1637_DATA_WRITE = 0x00; //!< Write data
const uint8_t TM1637_DATA_READ_KEYS = 0x02; //!< Read keys
const uint8_t TM1637_DATA_AUTO_INC_ADDR = 0x00; //!< Auto increment address
const uint8_t TM1637_DATA_FIXED_ADDR = 0x04; //!< Fixed address
//
// A
// ---
@@ -138,6 +144,36 @@ void TM1637Display::dump_config() {
LOG_UPDATE_INTERVAL(this);
}
#ifdef USE_BINARY_SENSOR
void TM1637Display::loop() {
uint8_t val = this->get_keys();
for (auto *tm1637_key : this->tm1637_keys_)
tm1637_key->process(val);
}
uint8_t TM1637Display::get_keys() {
this->start_();
this->send_byte_(TM1637_CMD_DATA | TM1637_DATA_READ_KEYS);
this->start_();
uint8_t key_code = read_byte_();
this->stop_();
if (key_code != 0xFF) {
// Invert key_code:
// Bit | 7 6 5 4 3 2 1 0
// ------+-------------------------
// From | S0 S1 S2 K1 K2 1 1 1
// To | S0 S1 S2 K1 K2 0 0 0
key_code = ~key_code;
// Shift bits to:
// Bit | 7 6 5 4 3 2 1 0
// ------+------------------------
// To | 0 0 0 0 K2 S2 S1 S0
key_code = (uint8_t)((key_code & 0x80) >> 7 | (key_code & 0x40) >> 5 | (key_code & 0x20) >> 3 | (key_code & 0x08));
}
return key_code;
}
#endif
void TM1637Display::update() {
for (uint8_t &i : this->buffer_)
i = 0;
@@ -165,14 +201,14 @@ void TM1637Display::stop_() {
void TM1637Display::display() {
ESP_LOGVV(TAG, "Display %02X%02X%02X%02X", buffer_[0], buffer_[1], buffer_[2], buffer_[3]);
// Write COMM1
// Write DATA CMND
this->start_();
this->send_byte_(TM1637_I2C_COMM1);
this->send_byte_(TM1637_CMD_DATA);
this->stop_();
// Write COMM2 + first digit address
// Write ADDR CMD + first digit address
this->start_();
this->send_byte_(TM1637_I2C_COMM2);
this->send_byte_(TM1637_CMD_ADDR);
// Write the data bytes
if (this->inverted_) {
@@ -187,20 +223,17 @@ void TM1637Display::display() {
this->stop_();
// Write COMM3 + brightness
// Write display CTRL CMND + brightness
this->start_();
this->send_byte_(TM1637_I2C_COMM3 + ((this->intensity_ & 0x7) | 0x08));
this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | 0x08));
this->stop_();
}
bool TM1637Display::send_byte_(uint8_t b) {
uint8_t data = b;
// 8 Data Bits
for (uint8_t i = 0; i < 8; i++) {
// CLK low
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->bit_delay_();
// Set data bit
if (data & 0x01) {
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
@@ -209,19 +242,16 @@ bool TM1637Display::send_byte_(uint8_t b) {
}
this->bit_delay_();
// CLK high
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
data = data >> 1;
}
// Wait for acknowledge
// CLK to zero
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
// CLK to high
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
@@ -237,8 +267,38 @@ bool TM1637Display::send_byte_(uint8_t b) {
return ack;
}
uint8_t TM1637Display::read_byte_() {
uint8_t retval = 0;
// Prepare DIO to read data
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
// Data is shifted out by the TM1637 on the CLK falling edge
for (uint8_t bit = 0; bit < 8; bit++) {
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
// Read next bit
retval <<= 1;
if (this->dio_pin_->digital_read()) {
retval |= 0x01;
}
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->bit_delay_();
}
// Return DIO to output mode
// Dummy ACK
this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->bit_delay_();
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->bit_delay_();
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
this->bit_delay_();
return retval;
}
uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
// ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
uint8_t pos = start_pos;
for (; *str != '\0'; str++) {
uint8_t data = TM1637_UNKNOWN_CHAR;

View File

@@ -8,10 +8,17 @@
#include "esphome/components/time/real_time_clock.h"
#endif
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
namespace esphome {
namespace tm1637 {
class TM1637Display;
#ifdef USE_BINARY_SENSOR
class TM1637Key;
#endif
using tm1637_writer_t = std::function<void(TM1637Display &)>;
@@ -46,10 +53,15 @@ class TM1637Display : public PollingComponent {
void display();
#ifdef USE_BINARY_SENSOR
void loop() override;
uint8_t get_keys();
void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); }
#endif
#ifdef USE_TIME
/// Evaluate the strftime-format and print the result at the given position.
uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0.
uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
@@ -58,6 +70,7 @@ class TM1637Display : public PollingComponent {
void bit_delay_();
void setup_pins_();
bool send_byte_(uint8_t b);
uint8_t read_byte_();
void start_();
void stop_();
@@ -68,7 +81,23 @@ class TM1637Display : public PollingComponent {
bool inverted_;
optional<tm1637_writer_t> writer_{};
uint8_t buffer_[6] = {0};
#ifdef USE_BINARY_SENSOR
std::vector<TM1637Key *> tm1637_keys_{};
#endif
};
#ifdef USE_BINARY_SENSOR
class TM1637Key : public binary_sensor::BinarySensor {
friend class TM1637Display;
public:
void set_keycode(uint8_t key_code) { key_code_ = key_code; }
void process(uint8_t data) { this->publish_state(static_cast<bool>(data == this->key_code_)); }
protected:
uint8_t key_code_{0};
};
#endif
} // namespace tm1637
} // namespace esphome

View File

@@ -4,6 +4,7 @@ import esphome.codegen as cg
from esphome.components import display
from esphome import automation
from esphome.const import CONF_ON_TOUCH
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["display"]
@@ -39,3 +40,9 @@ async def register_touchscreen(var, config):
[(TouchPoint, "touch")],
config[CONF_ON_TOUCH],
)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(touchscreen_ns.using)
cg.add_define("USE_TOUCHSCREEN")

View File

@@ -14,6 +14,8 @@ from esphome.const import (
CONF_PASSWORD,
CONF_INCLUDE_INTERNAL,
CONF_OTA,
CONF_VERSION,
CONF_LOCAL,
)
from esphome.core import CORE, coroutine_with_priority
@@ -22,18 +24,37 @@ AUTO_LOAD = ["json", "web_server_base"]
web_server_ns = cg.esphome_ns.namespace("web_server")
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
def default_url(config):
config = config.copy()
if config[CONF_VERSION] == 1:
if not (CONF_CSS_URL in config):
config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css"
if not (CONF_JS_URL in config):
config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js"
if config[CONF_VERSION] == 2:
if not (CONF_CSS_URL in config):
config[CONF_CSS_URL] = ""
if not (CONF_JS_URL in config):
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
return config
def validate_local(config):
if CONF_LOCAL in config and config[CONF_VERSION] == 1:
raise cv.Invalid("'local' is not supported in version 1")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(WebServer),
cv.Optional(CONF_PORT, default=80): cv.port,
cv.Optional(
CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"
): cv.string,
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2),
cv.Optional(CONF_CSS_URL): cv.string,
cv.Optional(CONF_CSS_INCLUDE): cv.file_,
cv.Optional(
CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"
): cv.string,
cv.Optional(CONF_JS_URL): cv.string,
cv.Optional(CONF_JS_INCLUDE): cv.file_,
cv.Optional(CONF_AUTH): cv.Schema(
{
@@ -50,9 +71,12 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
cv.Optional(CONF_OTA, default=True): cv.boolean,
},
cv.Optional(CONF_LOCAL): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
default_url,
validate_local,
)
@@ -68,6 +92,7 @@ async def to_code(config):
cg.add(paren.set_port(config[CONF_PORT]))
cg.add_define("USE_WEBSERVER")
cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT])
cg.add_define("USE_WEBSERVER_VERSION", config[CONF_VERSION])
cg.add(var.set_css_url(config[CONF_CSS_URL]))
cg.add(var.set_js_url(config[CONF_JS_URL]))
cg.add(var.set_allow_ota(config[CONF_OTA]))
@@ -75,13 +100,15 @@ async def to_code(config):
cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME]))
cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD]))
if CONF_CSS_INCLUDE in config:
cg.add_define("WEBSERVER_CSS_INCLUDE")
cg.add_define("USE_WEBSERVER_CSS_INCLUDE")
path = CORE.relative_config_path(config[CONF_CSS_INCLUDE])
with open(file=path, encoding="utf-8") as myfile:
cg.add(var.set_css_include(myfile.read()))
if CONF_JS_INCLUDE in config:
cg.add_define("WEBSERVER_JS_INCLUDE")
cg.add_define("USE_WEBSERVER_JS_INCLUDE")
path = CORE.relative_config_path(config[CONF_JS_INCLUDE])
with open(file=path, encoding="utf-8") as myfile:
cg.add(var.set_js_include(myfile.read()))
cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
if CONF_LOCAL in config and config[CONF_LOCAL]:
cg.add_define("USE_WEBSERVER_LOCAL")

View File

@@ -0,0 +1,97 @@
#ifdef USE_ARDUINO
#include "list_entities.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include "web_server.h"
namespace esphome {
namespace web_server {
ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(web_server) {}
#ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->web_server_->events_.send(
this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) {
this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(),
"state");
return true;
}
#endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) {
this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
this->web_server_->events_.send(
this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) {
this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) {
this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
} // namespace web_server
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -0,0 +1,60 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace web_server {
class WebServer;
class ListEntitiesIterator : public ComponentIterator {
public:
ListEntitiesIterator(WebServer *web_server);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::Fan *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *sensor) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
protected:
WebServer *web_server_;
};
} // namespace web_server
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -0,0 +1,573 @@
#pragma once
// Generated from https://github.com/esphome/esphome-webserver
#include "esphome/core/hal.h"
namespace esphome {
namespace web_server {
const uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3,
0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0xac, 0x56, 0x01, 0x16, 0x08, 0x91, 0x54, 0x6d, 0x06, 0x05, 0xf2, 0xca, 0x55,
0xe5, 0x5b, 0x65, 0xd7, 0xe6, 0x92, 0xaa, 0xbc, 0xc8, 0x74, 0x09, 0x22, 0x93, 0x22, 0x5c, 0x20, 0x40, 0x03, 0x49,
0x2d, 0xa6, 0xd0, 0xa7, 0x9f, 0xfa, 0x69, 0xce, 0x99, 0xf5, 0xa1, 0x5f, 0xe6, 0xf4, 0xcb, 0x7c, 0xc4, 0x7c, 0xcf,
0xfd, 0x81, 0xe9, 0x4f, 0x98, 0x88, 0xc8, 0x05, 0x09, 0x90, 0x5a, 0xec, 0xf6, 0xdc, 0x53, 0x8b, 0x80, 0x5c, 0x23,
0x23, 0x23, 0x63, 0x4f, 0x68, 0x6f, 0x63, 0x9c, 0x8d, 0xf8, 0xe5, 0x9c, 0x59, 0x53, 0x3e, 0x4b, 0xfa, 0x7b, 0xf2,
0x7f, 0x16, 0x8d, 0xfb, 0x7b, 0x49, 0x9c, 0x7e, 0xb2, 0x72, 0x96, 0x84, 0xf1, 0x28, 0x4b, 0xad, 0x69, 0xce, 0x26,
0xe1, 0x38, 0xe2, 0x51, 0x10, 0xcf, 0xa2, 0x53, 0x66, 0xed, 0xf4, 0xf7, 0x66, 0x8c, 0x47, 0xd6, 0x68, 0x1a, 0xe5,
0x05, 0xe3, 0xe1, 0xfb, 0xc3, 0xaf, 0x5a, 0x8f, 0xfb, 0x7b, 0xc5, 0x28, 0x8f, 0xe7, 0xdc, 0xc2, 0x21, 0xc3, 0x59,
0x36, 0x5e, 0x24, 0xac, 0x7f, 0x16, 0xe5, 0xd6, 0x3e, 0x0b, 0xdf, 0x9c, 0xfc, 0xc2, 0x46, 0xdc, 0x1f, 0xb3, 0x49,
0x9c, 0xb2, 0xb7, 0x79, 0x36, 0x67, 0x39, 0xbf, 0xf4, 0x2e, 0xd6, 0x57, 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x53,
0xc6, 0xdf, 0x9c, 0xa7, 0xaa, 0xcf, 0x53, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0xe3, 0xd7, 0xb4, 0x39, 0xb8, 0x9c, 0x9d,
0x64, 0x49, 0xe1, 0x1d, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, 0xb4, 0xf4, 0x3e, 0xad,
0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x24, 0x61, 0x5e, 0xc1, 0x42, 0x87, 0x79,
0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0x60, 0x9f, 0x51, 0xc9, 0x92, 0xe9, 0x56, 0xc1, 0x46, 0xdb,
0x03, 0x74, 0x4d, 0xe2, 0xd3, 0x85, 0x7e, 0x3f, 0xcf, 0x63, 0xae, 0x9e, 0xcf, 0xa2, 0x64, 0xc1, 0x82, 0xb8, 0x74,
0x03, 0x76, 0xc4, 0x87, 0x61, 0xec, 0x3d, 0xa1, 0x41, 0x61, 0xc8, 0xe5, 0x24, 0xcb, 0x1d, 0xc4, 0x55, 0x8c, 0x63,
0xf3, 0xab, 0x2b, 0x87, 0x87, 0xcb, 0xd2, 0x75, 0x0f, 0x98, 0x3f, 0x8a, 0x92, 0xc4, 0xc1, 0x89, 0xb7, 0xb6, 0x0a,
0x9c, 0x31, 0xf6, 0xf8, 0x51, 0x3c, 0x74, 0x7b, 0xf1, 0xc4, 0xe1, 0xcc, 0xad, 0xfa, 0x65, 0x13, 0x8b, 0x33, 0x87,
0xbb, 0xee, 0xa7, 0xeb, 0xfb, 0xe4, 0x8c, 0x2f, 0x72, 0x80, 0xbd, 0xf4, 0xde, 0xa8, 0x99, 0x2f, 0xb0, 0xfe, 0x19,
0x75, 0xec, 0x01, 0xec, 0x05, 0xb7, 0x3e, 0x84, 0xe7, 0x71, 0x3a, 0xce, 0xce, 0xfd, 0x83, 0x69, 0x04, 0x3f, 0xde,
0x65, 0x19, 0xdf, 0xda, 0x72, 0xce, 0xb2, 0x78, 0x6c, 0xb5, 0xc3, 0xd0, 0xac, 0xbc, 0x7c, 0x72, 0x70, 0x70, 0x75,
0xd5, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0x67, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x39, 0xe7, 0x6c, 0x7c, 0xc0, 0x2f,
0x13, 0x28, 0x65, 0x8c, 0x17, 0x36, 0xac, 0xf1, 0x69, 0x36, 0x02, 0xb4, 0xa5, 0x06, 0xe2, 0xa1, 0x69, 0xce, 0xe6,
0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0xd8, 0x5e, 0xc7, 0xf5, 0x62, 0x16,
0xa6, 0xec, 0xdc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0xa0, 0xd7, 0x25, 0x2d, 0x21, 0x5f, 0x8c, 0x80,
0x40, 0x68, 0x81, 0x4b, 0x44, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x9b, 0xa3, 0xa2, 0x78, 0xc7, 0x8a, 0x45, 0xc2, 0x37,
0x43, 0xd8, 0x0b, 0xbe, 0x11, 0x86, 0x5f, 0xb9, 0x7c, 0x9a, 0x67, 0xe7, 0xd6, 0xb3, 0x3c, 0x87, 0xe6, 0x36, 0x4c,
0x29, 0x1a, 0x58, 0x71, 0x61, 0xa5, 0x19, 0xb7, 0xf4, 0x60, 0xb8, 0x81, 0xbe, 0xf5, 0xbe, 0x60, 0xd6, 0xf1, 0x22,
0x2d, 0xa2, 0x09, 0x83, 0xa6, 0xc7, 0x56, 0x96, 0x5b, 0xc7, 0x30, 0xe8, 0x31, 0x6c, 0x59, 0xc1, 0xe1, 0xd4, 0xf8,
0xb6, 0xdb, 0xa3, 0xb9, 0xa0, 0xf0, 0x90, 0x5d, 0xf0, 0x90, 0x95, 0x40, 0x98, 0x56, 0xa1, 0x97, 0xe1, 0xb8, 0xcb,
0x04, 0x0a, 0x58, 0x18, 0x33, 0x24, 0x59, 0xc7, 0x6c, 0xac, 0x37, 0xe7, 0xc3, 0xd6, 0x96, 0xc6, 0x35, 0xe0, 0xc4,
0x81, 0xb6, 0x45, 0xa3, 0xad, 0x27, 0x16, 0x5e, 0x43, 0x91, 0xeb, 0x31, 0x5f, 0xa2, 0xef, 0xe0, 0x32, 0x1d, 0xd5,
0xc7, 0x86, 0xca, 0x92, 0x67, 0x07, 0x3c, 0x8f, 0xd3, 0x53, 0x00, 0x42, 0xce, 0x64, 0x36, 0x29, 0x4b, 0xb1, 0xf9,
0x4f, 0x58, 0xc8, 0xc2, 0x3e, 0x8e, 0x9e, 0x33, 0xc7, 0x2e, 0xa8, 0x87, 0x1d, 0x86, 0x88, 0x7a, 0x20, 0x30, 0x36,
0x60, 0x01, 0xdb, 0xb6, 0x6d, 0xef, 0x2b, 0xd7, 0x3b, 0x47, 0x0a, 0xf2, 0x7d, 0x9f, 0xc8, 0x57, 0x74, 0x8e, 0xc3,
0x0e, 0x02, 0xed, 0x27, 0x2c, 0x3d, 0xe5, 0xd3, 0x01, 0x3b, 0x6a, 0x0f, 0x03, 0x0e, 0x50, 0x8d, 0x17, 0x23, 0xe6,
0x20, 0x3d, 0x7a, 0x05, 0x1e, 0x9f, 0x6d, 0x07, 0xa6, 0xc0, 0x8d, 0xd9, 0xa0, 0x35, 0xd6, 0xb6, 0xc6, 0x55, 0x24,
0xaa, 0x00, 0x43, 0x3a, 0xb7, 0xe1, 0x84, 0x9d, 0xb0, 0xdc, 0x80, 0x43, 0x37, 0xeb, 0xd5, 0x76, 0x70, 0x01, 0x3b,
0x04, 0xfd, 0xac, 0xc9, 0x22, 0x1d, 0xf1, 0x18, 0x18, 0x97, 0xbd, 0x0d, 0xe0, 0x8a, 0x9d, 0xd3, 0x1b, 0x67, 0xbb,
0xa5, 0xeb, 0xc4, 0xee, 0x36, 0x3b, 0x2a, 0xb6, 0x3b, 0x43, 0x0f, 0xa1, 0xd4, 0xc8, 0x97, 0x0b, 0x8f, 0x61, 0x81,
0x70, 0x46, 0x98, 0x3e, 0x9e, 0x1f, 0x06, 0xcc, 0x5f, 0xa5, 0xe3, 0x90, 0xfb, 0xb3, 0x68, 0x8e, 0xab, 0x61, 0x44,
0x03, 0x51, 0x3a, 0x42, 0xe8, 0x6a, 0xfb, 0x82, 0x18, 0xf3, 0x2b, 0x12, 0x70, 0x01, 0x21, 0x70, 0x66, 0x9f, 0x45,
0xa3, 0x29, 0x1c, 0xf1, 0x0a, 0x71, 0x63, 0x75, 0x1c, 0x46, 0x39, 0x8b, 0x38, 0x7b, 0x96, 0x30, 0x7c, 0xc3, 0x1d,
0x80, 0x9e, 0xb6, 0xeb, 0x15, 0xea, 0xdc, 0x25, 0x31, 0x7f, 0x9d, 0xc1, 0x3c, 0x3d, 0x41, 0x24, 0x40, 0xc5, 0xc5,
0xd6, 0x56, 0x8c, 0x24, 0xb2, 0xcf, 0x61, 0xb7, 0x4e, 0x16, 0xc0, 0x04, 0xec, 0x14, 0x5b, 0xd8, 0x80, 0x6d, 0x2f,
0xf6, 0x39, 0x20, 0xf1, 0x49, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, 0x14, 0xe4, 0x47, 0xf3, 0x39, 0x4b, 0xc7, 0x4f,
0xa6, 0x71, 0x32, 0x06, 0x6c, 0x94, 0xb0, 0xde, 0x8c, 0x85, 0xb0, 0x4e, 0x58, 0x4c, 0x70, 0xf3, 0x8a, 0x68, 0xfb,
0x90, 0x90, 0x79, 0x68, 0xdb, 0x3d, 0xe4, 0x40, 0x72, 0x15, 0xc8, 0x83, 0x68, 0xe3, 0xde, 0x01, 0xeb, 0x2f, 0x5c,
0xbe, 0x1d, 0xc6, 0x7a, 0x1b, 0x25, 0x82, 0x9f, 0x20, 0xa7, 0x01, 0xfc, 0x33, 0xe0, 0x81, 0x3d, 0x64, 0x5c, 0xdf,
0x49, 0xae, 0x93, 0x32, 0xb5, 0x42, 0x40, 0xc0, 0x08, 0x39, 0x88, 0xc4, 0xc1, 0xdb, 0x2c, 0xb9, 0x9c, 0xc4, 0x49,
0x72, 0xb0, 0x98, 0xcf, 0xb3, 0x9c, 0x7b, 0x5f, 0x87, 0x4b, 0x9e, 0x55, 0x6b, 0xa5, 0x43, 0x5e, 0x9c, 0xc7, 0x1c,
0x11, 0xea, 0x2e, 0x47, 0x11, 0x6c, 0xf5, 0x97, 0x59, 0x96, 0xb0, 0x28, 0x85, 0x65, 0xb0, 0x81, 0x6d, 0x07, 0xe9,
0x22, 0x49, 0x7a, 0x27, 0x30, 0xec, 0xa7, 0x1e, 0x55, 0x0b, 0x8e, 0x1f, 0xd0, 0xf3, 0x7e, 0x9e, 0x47, 0x97, 0xd0,
0x10, 0xdb, 0x00, 0x2d, 0xc2, 0x6e, 0x7d, 0x7d, 0xf0, 0xe6, 0xb5, 0x2f, 0x08, 0x3f, 0x9e, 0x5c, 0x02, 0xa0, 0x65,
0xc5, 0x35, 0x27, 0x79, 0x36, 0x6b, 0x4c, 0x8d, 0x78, 0x88, 0x43, 0xd6, 0xbb, 0x06, 0x84, 0x98, 0x46, 0x86, 0x5d,
0x62, 0x26, 0x04, 0xaf, 0x89, 0x9e, 0x65, 0x25, 0x9e, 0x81, 0x01, 0x3e, 0x04, 0xa2, 0x18, 0xa6, 0xbc, 0x19, 0x5a,
0x9e, 0x5f, 0x2e, 0xe3, 0x90, 0xe0, 0x9c, 0xa3, 0xfc, 0x45, 0x18, 0x47, 0x11, 0xcc, 0xbe, 0x14, 0x03, 0x96, 0x0a,
0xe2, 0xb8, 0x2c, 0xbd, 0x44, 0x13, 0x31, 0x72, 0x3c, 0x64, 0x28, 0x1c, 0x8e, 0xd1, 0xd5, 0x15, 0x83, 0x17, 0xd7,
0xfb, 0x26, 0x5c, 0x46, 0x6a, 0x3d, 0x28, 0xa1, 0xf0, 0x7c, 0x05, 0x82, 0x4f, 0xa0, 0x24, 0x3b, 0x03, 0x39, 0x08,
0x70, 0x7e, 0xed, 0x81, 0xfc, 0x4f, 0x10, 0x8a, 0x8d, 0x8e, 0x07, 0x12, 0xf4, 0xc9, 0x34, 0x4a, 0x4f, 0xd9, 0x38,
0x48, 0x58, 0x29, 0x39, 0xef, 0xbe, 0x05, 0x7b, 0x0c, 0xe4, 0x54, 0x58, 0xcf, 0x0f, 0x5f, 0xbd, 0x94, 0x3b, 0x57,
0x63, 0xc6, 0xb0, 0x49, 0x0b, 0x10, 0xab, 0xc0, 0xb6, 0x25, 0x3b, 0x7e, 0xc6, 0x15, 0xf7, 0x16, 0x25, 0x71, 0xf1,
0x7e, 0x0e, 0x2a, 0x06, 0x7b, 0x0b, 0xc3, 0xc0, 0xf4, 0x21, 0x4c, 0x45, 0xe5, 0x30, 0x9f, 0xa8, 0x18, 0xeb, 0x22,
0xe8, 0x2c, 0x56, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x54, 0x79, 0x3c, 0xb2, 0xa2, 0xf1, 0xf8, 0x45, 0x1a, 0xf3,
0x38, 0x4a, 0xe2, 0xdf, 0x08, 0x93, 0x4b, 0xa4, 0x31, 0xde, 0x93, 0x9b, 0x00, 0x6b, 0xa7, 0x1e, 0x89, 0xab, 0x98,
0xec, 0x06, 0x21, 0x43, 0x70, 0xcb, 0x24, 0x3c, 0x1a, 0x4a, 0xf0, 0x12, 0x7f, 0xbe, 0x28, 0xa6, 0x88, 0x58, 0x39,
0x30, 0x32, 0xf2, 0xec, 0xa4, 0x60, 0xf9, 0x19, 0x1b, 0x6b, 0x0a, 0x28, 0x60, 0x55, 0xd4, 0x1c, 0x94, 0x17, 0x9a,
0xd1, 0x51, 0x32, 0x94, 0xc1, 0x50, 0x3d, 0x93, 0xcd, 0x32, 0x49, 0xcc, 0x5a, 0xc3, 0xd1, 0x5c, 0xc0, 0x11, 0x4a,
0x85, 0xe4, 0x04, 0x45, 0xa8, 0x56, 0x38, 0x05, 0x2e, 0x04, 0x52, 0xc1, 0x3c, 0xe6, 0x4a, 0x92, 0x3d, 0x5b, 0x90,
0x48, 0x28, 0xa0, 0x23, 0x1c, 0x64, 0x82, 0xb4, 0x70, 0xe1, 0x54, 0x01, 0x97, 0x97, 0xe0, 0x0a, 0x2e, 0xa2, 0xd4,
0x1c, 0x24, 0x80, 0xf0, 0x1b, 0x21, 0x0b, 0x7d, 0x6c, 0x41, 0x64, 0xe0, 0xeb, 0x9d, 0x07, 0xc4, 0xca, 0x75, 0x57,
0x0b, 0xf1, 0xae, 0x01, 0x1b, 0x27, 0x46, 0x7a, 0xf2, 0x36, 0xb8, 0x9f, 0x66, 0xfb, 0xa3, 0x11, 0x2b, 0x8a, 0x2c,
0xdf, 0xda, 0xda, 0xa0, 0xf6, 0xd7, 0x29, 0x5a, 0x80, 0x49, 0x57, 0xf3, 0x3a, 0xbb, 0x20, 0x09, 0x6e, 0x8a, 0x15,
0x25, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0xe0, 0xd9, 0x9e, 0x44, 0x03, 0x60, 0x7d, 0x55, 0xf1, 0x13, 0xfa, 0x4c, 0x1d,
0x33, 0x6b, 0xf5, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa1, 0x6b, 0x87, 0xc6, 0xdd, 0x90, 0x02,
0x72, 0x79, 0x4a, 0x22, 0xdb, 0xd8, 0x46, 0xd0, 0xda, 0x4a, 0x8f, 0x50, 0xaf, 0x56, 0x93, 0x13, 0xa0, 0x47, 0x6c,
0xd8, 0x93, 0xf5, 0x61, 0x21, 0x30, 0x97, 0xb3, 0x5f, 0x17, 0xac, 0xe0, 0x82, 0x74, 0x61, 0xdc, 0x1c, 0xc6, 0x2d,
0x57, 0xb4, 0xc3, 0x9a, 0xee, 0xb8, 0x0e, 0xb6, 0x37, 0x73, 0x94, 0x63, 0x05, 0x52, 0xf2, 0xcd, 0xe4, 0x84, 0xb0,
0x32, 0xf7, 0xea, 0xea, 0x1b, 0x35, 0x48, 0xb5, 0x95, 0x5a, 0x07, 0x6a, 0xec, 0x89, 0xad, 0x9a, 0x8c, 0x6d, 0x57,
0x0a, 0xd4, 0x8d, 0x4e, 0xaf, 0x46, 0x07, 0x70, 0xe6, 0xda, 0x9a, 0xa4, 0x2b, 0x65, 0xfb, 0xad, 0xc2, 0xe9, 0x1b,
0x31, 0x32, 0x69, 0xa3, 0xec, 0x76, 0xea, 0x51, 0x27, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x8c, 0x61, 0x51, 0x67, 0x0c,
0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x89, 0x8a, 0x85, 0x6a, 0xaf, 0x95, 0x01, 0xc1, 0xc3, 0x23, 0x50, 0x4e, 0xd6,
0xda, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8c, 0x7a, 0xd7, 0x35, 0x90, 0x06, 0x04, 0x34, 0x19, 0x36, 0xc5, 0x1b, 0x77,
0x85, 0xd6, 0x54, 0x3f, 0x5f, 0x31, 0x68, 0x11, 0xa0, 0x5f, 0x97, 0x6b, 0xb6, 0x88, 0xe4, 0xa6, 0x24, 0x67, 0x85,
0x1f, 0x51, 0x26, 0xf6, 0x84, 0x04, 0x3c, 0x2c, 0x1e, 0xb6, 0xbf, 0xb1, 0x71, 0xb2, 0x15, 0x53, 0x6b, 0xe4, 0xc8,
0x53, 0x00, 0xcf, 0x24, 0x04, 0x80, 0x5d, 0xd2, 0xcf, 0xda, 0xc1, 0x42, 0xb4, 0x1d, 0x20, 0x1d, 0xf8, 0x93, 0x24,
0xe2, 0x4e, 0x67, 0xa7, 0xed, 0x02, 0x1d, 0x02, 0x13, 0x07, 0x19, 0x01, 0xea, 0x7d, 0xb5, 0x14, 0x86, 0x4b, 0x89,
0x5d, 0xee, 0x83, 0x52, 0x34, 0x8d, 0x27, 0xdc, 0xc9, 0x50, 0x88, 0xb8, 0x25, 0x4b, 0x40, 0xc8, 0xe8, 0x73, 0x05,
0x5c, 0x82, 0x0b, 0xee, 0x22, 0xaa, 0x35, 0x43, 0x53, 0x90, 0x12, 0x97, 0x22, 0x29, 0xa8, 0x20, 0x30, 0x98, 0x4a,
0x4f, 0x51, 0x14, 0xc8, 0xb7, 0x78, 0x20, 0x06, 0x0d, 0x56, 0x34, 0xca, 0x78, 0x10, 0xaf, 0x16, 0x82, 0x18, 0xf6,
0x79, 0xf6, 0x32, 0x3b, 0x67, 0xf9, 0x93, 0x08, 0x61, 0x0f, 0x44, 0xf7, 0x12, 0x38, 0x3d, 0x31, 0x74, 0xd6, 0x53,
0xb4, 0x72, 0x46, 0x8b, 0x86, 0x8d, 0x98, 0xc5, 0x28, 0x08, 0x41, 0xca, 0x11, 0xee, 0x53, 0x3c, 0x52, 0x74, 0xf6,
0x50, 0x94, 0x30, 0x4d, 0x5b, 0xfb, 0x2f, 0xeb, 0xb4, 0x05, 0x23, 0xcc, 0x15, 0xb5, 0xd6, 0x4f, 0xac, 0xeb, 0x49,
0xd9, 0xec, 0x48, 0xda, 0x32, 0x84, 0x19, 0xc8, 0x8f, 0xab, 0xab, 0x4a, 0x49, 0x07, 0x61, 0xaa, 0xb9, 0x39, 0x6a,
0x4e, 0xe2, 0x48, 0xb8, 0x25, 0x08, 0x23, 0x54, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x59, 0xe4, 0xc5, 0x3d, 0xa7, 0x21,
0x8e, 0x00, 0x8a, 0x59, 0x4d, 0x22, 0x0d, 0x78, 0xa0, 0x2b, 0x50, 0x28, 0x29, 0x69, 0xe4, 0x55, 0x4d, 0x04, 0xc4,
0xe9, 0x98, 0xe5, 0xc2, 0x40, 0x93, 0x32, 0x14, 0x26, 0x4c, 0x81, 0xa0, 0xd9, 0x18, 0x38, 0xbc, 0x5a, 0x00, 0xa8,
0x27, 0xfe, 0x34, 0x2b, 0xb8, 0xae, 0x33, 0xa1, 0x8f, 0xaf, 0xae, 0x62, 0x61, 0x2f, 0x22, 0x01, 0xe4, 0x6c, 0x96,
0x9d, 0xb1, 0x35, 0x50, 0xf7, 0xd4, 0x60, 0x26, 0xc8, 0xc6, 0x30, 0x20, 0x44, 0x41, 0xb4, 0xcc, 0x93, 0x78, 0xc4,
0xb4, 0x94, 0x9a, 0xf9, 0xa0, 0xd0, 0xb1, 0x0b, 0xe0, 0x11, 0xcc, 0xed, 0xf7, 0xfb, 0x6d, 0xaf, 0xe3, 0x96, 0x02,
0xe1, 0xcb, 0x15, 0x8c, 0xde, 0x20, 0x1f, 0xa5, 0x0a, 0xbe, 0x8e, 0x17, 0x70, 0xd7, 0x10, 0x8a, 0x5c, 0xd8, 0x49,
0x9e, 0x64, 0xc4, 0xae, 0x37, 0x86, 0x41, 0x39, 0x53, 0x8c, 0x1b, 0x55, 0x5c, 0x71, 0x6c, 0xdf, 0x69, 0xb4, 0x69,
0x72, 0x52, 0x27, 0x4c, 0x6d, 0x8c, 0xdc, 0xf3, 0x42, 0x5b, 0xc0, 0xe6, 0xf6, 0xa0, 0x96, 0x48, 0xd5, 0x40, 0xeb,
0x00, 0xa1, 0xb0, 0x74, 0x9d, 0x95, 0x25, 0x55, 0x9d, 0x25, 0x13, 0xd7, 0x07, 0xe8, 0x0d, 0x93, 0x60, 0xae, 0x43,
0xc1, 0x81, 0x64, 0x08, 0x1c, 0x2d, 0x32, 0xb1, 0x5f, 0x4f, 0x60, 0x7b, 0x4e, 0xa2, 0xd1, 0x27, 0x0d, 0x6e, 0x85,
0xf6, 0x26, 0x19, 0x38, 0x8d, 0x92, 0xd0, 0x60, 0x57, 0xe6, 0xba, 0x15, 0x87, 0xae, 0x1d, 0x14, 0x30, 0xc8, 0x56,
0xc8, 0xbe, 0xb9, 0xd1, 0x4d, 0x6a, 0x97, 0xe4, 0xa1, 0xec, 0x27, 0x4d, 0x25, 0x37, 0x90, 0x1c, 0x57, 0xdc, 0x80,
0x2b, 0xc2, 0x83, 0xad, 0x69, 0x40, 0x02, 0x74, 0x57, 0x8e, 0xe3, 0xe2, 0x7a, 0x14, 0xfc, 0xa9, 0x60, 0x3e, 0x35,
0x66, 0xba, 0x15, 0x52, 0xcd, 0xe1, 0xa4, 0x1a, 0xac, 0x41, 0x93, 0xca, 0x83, 0x62, 0x35, 0xdf, 0xa0, 0xa2, 0x42,
0x14, 0x7f, 0x2a, 0xaa, 0x50, 0x05, 0x43, 0x30, 0x0a, 0x2f, 0x97, 0x04, 0x97, 0xad, 0xb2, 0x16, 0xc9, 0x53, 0x63,
0x12, 0xa9, 0x9a, 0xe4, 0x32, 0x50, 0xb0, 0xe8, 0xb4, 0xfa, 0x52, 0x13, 0x57, 0x2c, 0x37, 0x0d, 0x35, 0x33, 0xc9,
0x95, 0x35, 0xe1, 0x14, 0x68, 0x77, 0x29, 0xed, 0xdd, 0x5c, 0x4f, 0xa1, 0xd6, 0x53, 0xf8, 0x86, 0x0d, 0x65, 0xd2,
0x76, 0x3e, 0x00, 0x75, 0xbf, 0x56, 0x89, 0xfa, 0xa9, 0x8f, 0x8c, 0xd9, 0xd5, 0x4c, 0x17, 0x18, 0x8a, 0x24, 0x93,
0x74, 0x20, 0xe9, 0x0d, 0xd9, 0x46, 0x65, 0x19, 0x65, 0xae, 0x38, 0x20, 0x35, 0xab, 0x34, 0xf3, 0x52, 0xb7, 0xa1,
0xbf, 0x97, 0xa5, 0xc4, 0x13, 0x17, 0x98, 0x89, 0xbd, 0x9b, 0x70, 0xe3, 0xa5, 0x61, 0x26, 0xb4, 0x5f, 0xa1, 0xec,
0xd4, 0x30, 0x94, 0x4a, 0x16, 0x88, 0x63, 0xe3, 0x6b, 0xa5, 0x19, 0x64, 0xfe, 0x1a, 0x7d, 0x0a, 0x40, 0x49, 0x60,
0xf3, 0x35, 0x96, 0xbc, 0x28, 0xac, 0xe3, 0x71, 0x83, 0xf0, 0x58, 0xb1, 0xd0, 0x1a, 0xcb, 0xd7, 0xf2, 0x2c, 0xf6,
0x6b, 0x26, 0xa1, 0x89, 0xc9, 0x62, 0x50, 0x04, 0xb6, 0x72, 0x44, 0x54, 0xb2, 0x2d, 0x19, 0x24, 0x64, 0x90, 0xae,
0x22, 0xbd, 0x36, 0x92, 0x81, 0xeb, 0x54, 0x70, 0xb4, 0x74, 0x18, 0x46, 0x0e, 0x1a, 0xee, 0xb4, 0x17, 0x2b, 0x88,
0x6c, 0xea, 0x9b, 0x44, 0x8a, 0x68, 0x9c, 0x16, 0xa8, 0xc2, 0x99, 0x32, 0xdd, 0x71, 0x60, 0x39, 0xc0, 0xf6, 0x57,
0x48, 0x6f, 0xad, 0xda, 0xe9, 0xfa, 0x95, 0xc1, 0x77, 0x75, 0x95, 0x20, 0x3d, 0x08, 0x85, 0x17, 0xf6, 0x6c, 0xa0,
0x78, 0xef, 0xfe, 0x4b, 0x6c, 0x45, 0xfa, 0x67, 0x55, 0x52, 0x59, 0x0a, 0x35, 0xca, 0xad, 0xef, 0x13, 0x33, 0x5d,
0x8b, 0xaa, 0xe2, 0xc0, 0xe0, 0xea, 0x07, 0x4a, 0x60, 0x57, 0x4b, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x05,
0x19, 0x2f, 0x1b, 0xeb, 0x4c, 0xc8, 0xad, 0x2d, 0xd3, 0x66, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x38, 0x17, 0x97,
0x29, 0x69, 0x30, 0x4f, 0x94, 0x38, 0x5a, 0x31, 0x40, 0xdb, 0x3d, 0xb4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e,
0x45, 0x58, 0xda, 0xc8, 0x9f, 0xa4, 0xd2, 0x01, 0xeb, 0x42, 0x15, 0x92, 0x8c, 0x70, 0x53, 0x17, 0x2d, 0x46, 0x53,
0x86, 0x2e, 0x70, 0xa5, 0x4f, 0x98, 0xbc, 0x67, 0x03, 0xd7, 0x2d, 0x06, 0x66, 0xeb, 0x61, 0x2f, 0x9b, 0xdd, 0x6b,
0xea, 0x3f, 0xec, 0x11, 0xf0, 0xb6, 0x99, 0xaa, 0x2b, 0x1b, 0xef, 0x92, 0x45, 0xa2, 0x87, 0x6d, 0xdd, 0xd8, 0x52,
0xd7, 0xef, 0x35, 0xcc, 0xeb, 0xca, 0x30, 0xaf, 0x09, 0xd5, 0x86, 0x1c, 0x56, 0x66, 0x0e, 0x33, 0x0d, 0x79, 0xb1,
0x83, 0x6e, 0x4f, 0x38, 0x85, 0xc0, 0x88, 0xd0, 0xfa, 0xa0, 0xa2, 0x06, 0x42, 0x25, 0x57, 0x52, 0x35, 0x5b, 0x24,
0x63, 0x09, 0x2c, 0x98, 0xb0, 0x5c, 0xd2, 0xd1, 0x79, 0x9c, 0x24, 0x55, 0xe9, 0x9f, 0xca, 0xe0, 0xc5, 0xb0, 0xb7,
0xb1, 0x76, 0xb1, 0xa2, 0x85, 0x02, 0xc1, 0xd5, 0x4a, 0xd8, 0x7b, 0xc7, 0xad, 0xf6, 0x5d, 0x78, 0x1c, 0xb9, 0xe9,
0x8d, 0x80, 0x7a, 0xf4, 0xb0, 0x6a, 0xd2, 0xde, 0x7f, 0x86, 0x2e, 0x35, 0x63, 0x3d, 0x28, 0xce, 0xa8, 0xf8, 0x77,
0xe9, 0x53, 0xbf, 0x73, 0x79, 0xb7, 0x8a, 0xae, 0xa6, 0x43, 0x45, 0x39, 0x3e, 0x4c, 0x17, 0x4b, 0x5b, 0x39, 0x02,
0x72, 0x3d, 0x2c, 0x72, 0x01, 0x13, 0x35, 0x58, 0x50, 0x8a, 0x55, 0x6b, 0x61, 0xf7, 0xf2, 0x36, 0x67, 0x0e, 0xb9,
0xc2, 0x45, 0xff, 0x27, 0xd9, 0x6c, 0x8e, 0x9a, 0x59, 0x83, 0xa8, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa6, 0xac,
0xd6, 0x87, 0x4e, 0x64, 0x8d, 0x9e, 0xb4, 0xa1, 0x0c, 0x06, 0xd5, 0x42, 0x17, 0xd5, 0xf5, 0xe6, 0x26, 0x8b, 0x59,
0x47, 0xe3, 0x3e, 0xc9, 0x6d, 0xad, 0x4d, 0x7a, 0x1a, 0x07, 0xc4, 0x93, 0x24, 0xc1, 0x9b, 0x04, 0x50, 0x56, 0xc8,
0x59, 0x96, 0x0d, 0xf4, 0x2d, 0xcb, 0x12, 0xf7, 0xef, 0xdb, 0xde, 0x7e, 0xcd, 0xb2, 0xf6, 0xf6, 0xaf, 0x37, 0x91,
0xab, 0x3a, 0x69, 0x41, 0x1e, 0x0d, 0xa1, 0x68, 0x45, 0xa7, 0x0c, 0x97, 0xb3, 0x6c, 0xcc, 0x02, 0x1b, 0xba, 0xa7,
0x76, 0xa9, 0xa4, 0x32, 0x1c, 0x8e, 0x94, 0x39, 0xcb, 0x77, 0x75, 0x4f, 0x6a, 0xb0, 0x0f, 0x24, 0xa0, 0xd5, 0x85,
0xef, 0xc2, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0x21, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xc9, 0x48, 0x1a, 0xab, 0x21,
0xa4, 0xbe, 0x1b, 0x7c, 0x17, 0xdc, 0xde, 0xa3, 0xb2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0xf2, 0x88, 0xbd,
0x34, 0xa7, 0x03, 0x89, 0x53, 0x00, 0x66, 0x0e, 0x41, 0x92, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0x83, 0x97, 0xca, 0xa4,
0xce, 0xc0, 0x84, 0x00, 0x23, 0x3f, 0x89, 0x79, 0x0b, 0xe3, 0x91, 0xb6, 0xb7, 0x14, 0x15, 0xe8, 0x57, 0x24, 0xbf,
0x74, 0xa9, 0xac, 0x41, 0xef, 0x63, 0x78, 0x0c, 0xcd, 0x36, 0x37, 0x97, 0xce, 0xab, 0x88, 0x4f, 0xfd, 0x3c, 0x4a,
0xc7, 0xd9, 0xcc, 0x71, 0xb7, 0x6d, 0xdb, 0xf5, 0x0b, 0xb2, 0x44, 0xbe, 0x70, 0xcb, 0xcd, 0x63, 0x6f, 0xca, 0x42,
0x7b, 0x60, 0x6f, 0x7f, 0xf4, 0xde, 0xb2, 0xf0, 0x78, 0x6f, 0x73, 0x39, 0x65, 0x65, 0xff, 0xd8, 0xbb, 0xd0, 0x3e,
0x77, 0xef, 0x2d, 0x72, 0x19, 0xe8, 0x15, 0xf6, 0x2f, 0x24, 0x18, 0x40, 0x6e, 0xe4, 0x7f, 0x07, 0x2e, 0xf7, 0x9e,
0x02, 0x22, 0xd2, 0x4f, 0x7b, 0x75, 0x65, 0x67, 0xe4, 0x31, 0xb0, 0x37, 0xb4, 0xb1, 0xba, 0xb5, 0x55, 0x89, 0xf9,
0xaa, 0xd4, 0x1b, 0xb1, 0xb0, 0x66, 0xa9, 0x7b, 0xef, 0x29, 0xb4, 0x52, 0x3f, 0xc8, 0x23, 0x46, 0x42, 0x73, 0x55,
0x4f, 0x70, 0x8c, 0x23, 0xbe, 0xfe, 0x58, 0x1f, 0x09, 0x2f, 0x85, 0x1f, 0x83, 0xf6, 0x12, 0x81, 0xf8, 0x06, 0x03,
0xc7, 0x3b, 0x0c, 0x77, 0xf6, 0x9c, 0x41, 0xe0, 0x6c, 0xb4, 0x5a, 0x57, 0x3f, 0xed, 0x1c, 0xfd, 0x1c, 0xb5, 0x7e,
0xdb, 0x6f, 0xfd, 0x38, 0x74, 0xaf, 0x9c, 0x9f, 0x76, 0x06, 0x47, 0xf2, 0xed, 0xe8, 0xe7, 0xfe, 0x4f, 0xc5, 0xf0,
0x73, 0x51, 0xb8, 0xe9, 0xba, 0x3b, 0xa7, 0x60, 0x29, 0x85, 0x3b, 0xad, 0x56, 0x1f, 0x9e, 0x16, 0xf0, 0x84, 0x3f,
0x2f, 0xe1, 0xc7, 0xd5, 0x91, 0xf5, 0x1f, 0x7e, 0x4a, 0xff, 0xe3, 0x4f, 0xf9, 0x10, 0xc7, 0x3c, 0xfa, 0xf9, 0xa7,
0xc2, 0xbe, 0xd7, 0x0f, 0x77, 0x86, 0xdb, 0xae, 0xa3, 0x6b, 0x3e, 0x0f, 0xab, 0x47, 0x68, 0x75, 0xf4, 0xb3, 0x7c,
0xb3, 0xef, 0x1d, 0xef, 0xf5, 0xc3, 0xe1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x13, 0xe7, 0x99,
0xc3, 0xe8, 0xf7, 0xe0, 0xe7, 0x19, 0xfc, 0xb4, 0xe1, 0xe7, 0x29, 0xfc, 0xfc, 0x19, 0xba, 0x09, 0xff, 0xdb, 0x15,
0xf9, 0x42, 0xae, 0x30, 0x60, 0x11, 0xc1, 0x2e, 0xb8, 0x9b, 0x3b, 0xb1, 0x37, 0x21, 0xa4, 0xc1, 0x39, 0xf4, 0x7d,
0x1f, 0xdd, 0xa4, 0xce, 0xf2, 0xe3, 0x26, 0x6c, 0x3a, 0x52, 0xce, 0x66, 0xc0, 0x3c, 0xe1, 0x39, 0x28, 0x02, 0x2e,
0x62, 0xab, 0x05, 0x06, 0x57, 0xbd, 0x45, 0x38, 0x61, 0x0e, 0x28, 0x05, 0x87, 0x0c, 0x1f, 0xba, 0xae, 0xf7, 0x4c,
0xc6, 0x0c, 0xf1, 0x9c, 0x0b, 0xd2, 0x4a, 0x33, 0xa1, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, 0xc7, 0x3a, 0x3d,
0x85, 0xba, 0x4d, 0x11, 0x68, 0xfb, 0x8e, 0x45, 0x9f, 0xf0, 0x48, 0x3e, 0x37, 0x82, 0xc0, 0x2b, 0x9a, 0x7c, 0x53,
0x69, 0x34, 0x74, 0x44, 0x61, 0x8e, 0x7d, 0xc9, 0x60, 0x86, 0x15, 0x15, 0x91, 0x93, 0xd0, 0x14, 0x9a, 0x2d, 0x4c,
0xfe, 0x36, 0xca, 0xf9, 0x66, 0xa5, 0xd8, 0x86, 0x35, 0x4d, 0xb6, 0xa9, 0xe9, 0xdf, 0x61, 0x0a, 0x54, 0x2d, 0x29,
0xfe, 0x61, 0x8e, 0x1f, 0xa6, 0xb4, 0xac, 0xd7, 0x0e, 0x07, 0x0b, 0xbd, 0x00, 0xbe, 0x23, 0xfa, 0x39, 0x6f, 0x51,
0x8c, 0xc1, 0x5f, 0xe9, 0x66, 0xf0, 0xc4, 0x7c, 0xe8, 0xa2, 0x59, 0x96, 0xda, 0xb9, 0x95, 0x22, 0xbb, 0x7f, 0x81,
0x27, 0x23, 0x2d, 0xbd, 0x83, 0x50, 0x9d, 0x98, 0xc3, 0x9c, 0xb1, 0xef, 0xa2, 0xe4, 0x13, 0xcb, 0x9d, 0x0b, 0xaf,
0xd3, 0xfd, 0x82, 0x3a, 0x7b, 0xa8, 0x9b, 0xbd, 0xae, 0xc2, 0x68, 0x4a, 0x2d, 0x50, 0x21, 0xc2, 0x56, 0xc7, 0x43,
0x8e, 0x41, 0x28, 0xc8, 0xbd, 0x2c, 0xec, 0x12, 0x85, 0xdb, 0x7b, 0xc5, 0xd9, 0x69, 0xdf, 0x0e, 0x6c, 0x1b, 0x34,
0xfe, 0x43, 0x72, 0x5b, 0x09, 0xc5, 0x02, 0x14, 0xb2, 0xbd, 0xb8, 0xc7, 0xb7, 0xb7, 0x2b, 0x87, 0x13, 0x06, 0xd2,
0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x34, 0x84, 0x01, 0x47, 0xd0, 0x0c, 0xbb, 0xf4, 0x46, 0x7b, 0xb1, 0x9c, 0x06, 0x7d,
0x21, 0x7e, 0x12, 0x15, 0xfc, 0x05, 0xfa, 0x23, 0xc2, 0x11, 0x2a, 0xfb, 0x3e, 0xbb, 0x60, 0x23, 0xa5, 0x67, 0x00,
0xa2, 0x22, 0xb7, 0xe7, 0x8e, 0x42, 0xa3, 0x19, 0xcc, 0x1d, 0x86, 0x87, 0x03, 0x1b, 0xce, 0x12, 0x9c, 0xca, 0x30,
0x3a, 0xea, 0x0c, 0x07, 0x69, 0x08, 0xbc, 0x56, 0xe3, 0x56, 0x16, 0x2d, 0x6a, 0x45, 0xdd, 0xe1, 0xc0, 0x39, 0x05,
0x25, 0x1d, 0x74, 0x71, 0x07, 0xdf, 0xd0, 0x43, 0x91, 0x87, 0xef, 0xd8, 0xe9, 0xb3, 0x8b, 0xb9, 0x63, 0xef, 0xed,
0xd8, 0xdb, 0x58, 0xea, 0xd9, 0x40, 0x5e, 0x30, 0x77, 0x78, 0xe9, 0x9a, 0x9d, 0x77, 0x87, 0x08, 0x2a, 0x16, 0xe2,
0xe4, 0x97, 0x03, 0xbb, 0x2f, 0xa6, 0x6e, 0xc3, 0xa0, 0xa9, 0xdc, 0x7e, 0xdc, 0xd1, 0x43, 0x5a, 0xaa, 0xea, 0xaa,
0xa0, 0x83, 0xb2, 0x6e, 0xe0, 0x4c, 0xcd, 0x45, 0xb4, 0x70, 0x32, 0x89, 0x05, 0x30, 0x78, 0xb0, 0x19, 0x4c, 0x6a,
0x74, 0xdb, 0x1d, 0x0e, 0x2e, 0x83, 0x7b, 0xf6, 0x3d, 0xf5, 0x72, 0xc6, 0x02, 0xb0, 0x2e, 0x68, 0xfa, 0x33, 0x94,
0x22, 0xf0, 0x73, 0xce, 0x60, 0x91, 0x97, 0x54, 0x34, 0x96, 0x45, 0x0b, 0x2c, 0x3a, 0x0c, 0x10, 0x54, 0x2f, 0xd7,
0xda, 0x9f, 0xd8, 0x93, 0x71, 0x48, 0xb0, 0x6f, 0x6d, 0xc1, 0xd6, 0x6c, 0x77, 0x86, 0x18, 0x6f, 0xc8, 0x79, 0xf1,
0x5d, 0xcc, 0x41, 0x24, 0xec, 0xf4, 0x6d, 0x77, 0x60, 0x5b, 0xb8, 0xb5, 0xbd, 0x6c, 0x3b, 0x14, 0x18, 0x8e, 0xb7,
0xdf, 0xb2, 0x60, 0xda, 0x0f, 0xdb, 0x03, 0xa7, 0x10, 0xa2, 0x23, 0xc1, 0xb8, 0xa5, 0xe0, 0xe0, 0x6d, 0x6f, 0x0a,
0x0c, 0x1d, 0x29, 0x77, 0xd3, 0xde, 0x56, 0x85, 0x50, 0xf4, 0x71, 0x7b, 0xec, 0x06, 0x31, 0xfc, 0x70, 0x5a, 0x48,
0x34, 0x53, 0xdd, 0x57, 0x4b, 0x66, 0x37, 0x18, 0x2b, 0x8d, 0x3c, 0x09, 0xb3, 0x6d, 0x07, 0x3d, 0xb4, 0xc0, 0x69,
0xf7, 0x06, 0x00, 0xc3, 0xb6, 0xa3, 0x28, 0x6d, 0x47, 0x91, 0x9a, 0xd2, 0xcf, 0x8f, 0xaa, 0xed, 0x60, 0x83, 0x88,
0xf9, 0x95, 0xf4, 0x01, 0xb0, 0x82, 0xc4, 0x2b, 0x86, 0x2a, 0xe6, 0xf5, 0xbc, 0x16, 0xdf, 0x5a, 0x2a, 0x56, 0xc4,
0x3c, 0x83, 0x43, 0xf1, 0x52, 0x9b, 0x61, 0x42, 0xdd, 0x9e, 0x23, 0x32, 0x34, 0xc9, 0x87, 0x6d, 0x20, 0x7a, 0xe5,
0x60, 0x4f, 0xcd, 0x63, 0x91, 0x84, 0x55, 0x73, 0xef, 0x08, 0x48, 0x7b, 0x18, 0xbe, 0x16, 0x11, 0xc7, 0x9e, 0xf2,
0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x08, 0x17, 0x47, 0x18, 0x11, 0x3a, 0xf0, 0x47, 0x8b, 0x1c, 0xf8, 0x01, 0x7f, 0x0d,
0x9a, 0x41, 0x28, 0x9b, 0xa2, 0xa1, 0x87, 0x21, 0x60, 0x8f, 0x16, 0xde, 0x70, 0x9b, 0x1b, 0xd5, 0xa8, 0x51, 0x92,
0xf2, 0x42, 0x81, 0xe1, 0x1e, 0x97, 0xa6, 0x3d, 0x32, 0x06, 0x19, 0x31, 0x76, 0x30, 0xe6, 0xef, 0x8f, 0xb0, 0x1a,
0x27, 0x28, 0xdc, 0x92, 0x4e, 0x5b, 0xc5, 0xfe, 0x0e, 0xfc, 0x14, 0x38, 0x38, 0xd6, 0x81, 0x9d, 0xb5, 0xb5, 0x95,
0xc8, 0x45, 0xed, 0xa5, 0x3d, 0x8a, 0x44, 0xa0, 0x3f, 0xb8, 0xf0, 0x53, 0xa8, 0x46, 0x14, 0x51, 0x11, 0x69, 0xa0,
0x66, 0x54, 0xad, 0x82, 0xef, 0xc8, 0xf4, 0xc0, 0x73, 0x74, 0x5b, 0x93, 0xa2, 0xa8, 0x1b, 0x0b, 0x5f, 0xbe, 0xeb,
0x52, 0x68, 0x0b, 0x03, 0x90, 0x82, 0xd0, 0x04, 0xc1, 0xb8, 0xe4, 0x94, 0xac, 0xe8, 0xef, 0xa3, 0xe1, 0x2b, 0x9f,
0x1e, 0x65, 0xdb, 0xdb, 0x43, 0x11, 0xb7, 0x20, 0xc2, 0xe1, 0x86, 0x77, 0x35, 0xae, 0x00, 0xa8, 0x4f, 0xe7, 0xc4,
0x75, 0xc7, 0xb4, 0x22, 0x4d, 0x97, 0x7c, 0x9f, 0x1c, 0x66, 0x00, 0x0c, 0xee, 0x38, 0x47, 0xfe, 0xe0, 0x2f, 0x43,
0x30, 0x8f, 0xfd, 0xcf, 0xdd, 0x1d, 0xc5, 0x68, 0x7a, 0x32, 0xa6, 0xb8, 0xa4, 0x18, 0x6b, 0xc7, 0x23, 0xdf, 0x68,
0x90, 0x7b, 0x29, 0xac, 0x00, 0xa4, 0x39, 0xf0, 0x84, 0x8a, 0x82, 0x90, 0xa2, 0x02, 0xdb, 0xc7, 0xc3, 0xcf, 0xf1,
0x64, 0xbf, 0x03, 0x0d, 0x6f, 0xa0, 0xdf, 0x9e, 0xc2, 0xdb, 0x5f, 0xf4, 0xdb, 0x97, 0x2c, 0xf8, 0xa5, 0x94, 0xae,
0xfb, 0xda, 0x14, 0x0f, 0xd5, 0x14, 0xa5, 0xd8, 0x22, 0x03, 0x87, 0xcc, 0x5d, 0xf5, 0xd9, 0x70, 0xb7, 0x04, 0x64,
0x28, 0xd6, 0x05, 0x3a, 0x5a, 0x74, 0x8a, 0xc8, 0x75, 0x4d, 0x54, 0x18, 0xb9, 0x04, 0xe6, 0x82, 0x2b, 0xba, 0x25,
0xe2, 0xec, 0xb7, 0xdd, 0x65, 0xad, 0x2d, 0xe9, 0x77, 0x6c, 0x36, 0xe7, 0x97, 0x07, 0x24, 0xe8, 0x03, 0x99, 0x36,
0x20, 0x62, 0xe7, 0xed, 0x5e, 0xbc, 0xc7, 0x7b, 0x31, 0x70, 0xf5, 0x42, 0x91, 0x18, 0x9e, 0x55, 0xef, 0x2d, 0x7a,
0x29, 0x4d, 0x62, 0xf2, 0x6a, 0xcb, 0xeb, 0xca, 0xe5, 0x6d, 0x6f, 0xc3, 0x02, 0x7b, 0x46, 0x57, 0x2e, 0xba, 0x96,
0xa5, 0xc0, 0x09, 0x40, 0xf4, 0xb8, 0x4e, 0x72, 0x44, 0x71, 0x98, 0xcd, 0x86, 0x8c, 0x83, 0xb9, 0x6b, 0x47, 0xc5,
0x31, 0xb1, 0xbb, 0x4c, 0xd8, 0x81, 0x95, 0x11, 0x95, 0xb7, 0x3a, 0xc2, 0x3b, 0x2c, 0xfa, 0x6b, 0xff, 0xf6, 0x47,
0x8f, 0x6d, 0x77, 0x5c, 0x90, 0x20, 0xb5, 0xb1, 0x1e, 0x55, 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x63, 0xa9, 0xcc, 0xb7,
0xb7, 0xcb, 0x7a, 0xa8, 0x56, 0x9d, 0xe0, 0x5a, 0x34, 0xe5, 0xa2, 0x99, 0x0d, 0xc2, 0x01, 0x89, 0x09, 0x14, 0x68,
0x6e, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0xe4, 0x4f, 0x21, 0xf3, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xd2, 0x1f, 0x19,
0x62, 0xd4, 0x93, 0x94, 0x15, 0x10, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x45, 0xb0, 0xf2, 0x67, 0x2a, 0x87, 0x46, 0x68,
0x20, 0x51, 0x68, 0xa8, 0x25, 0x4a, 0xf9, 0xcc, 0xc3, 0x18, 0xa4, 0xfd, 0x93, 0x9a, 0xef, 0x2b, 0x57, 0x4a, 0x47,
0x7e, 0x54, 0x0c, 0x03, 0xaa, 0x5f, 0x48, 0x0e, 0x36, 0x0d, 0xdf, 0x03, 0x19, 0x55, 0x86, 0x27, 0x31, 0xc2, 0xa7,
0x71, 0xce, 0xc8, 0x52, 0xd8, 0x94, 0x30, 0x4b, 0xd5, 0x36, 0x52, 0xed, 0x22, 0xd3, 0x09, 0xe5, 0xc2, 0xfc, 0x53,
0x23, 0x76, 0x91, 0x85, 0x2b, 0xad, 0x41, 0xfd, 0x78, 0x63, 0x02, 0x94, 0x5d, 0x5d, 0x65, 0xc2, 0xc6, 0x8d, 0x48,
0xdf, 0xd0, 0x15, 0xd3, 0x81, 0x5a, 0x54, 0xe0, 0x44, 0xa4, 0xf1, 0x50, 0x0c, 0x85, 0x46, 0x38, 0xa4, 0x28, 0x72,
0xe1, 0x1a, 0x87, 0xbe, 0x18, 0x68, 0xdb, 0x28, 0x0d, 0x9d, 0x04, 0x98, 0x80, 0x58, 0xbb, 0xa1, 0x4d, 0xa5, 0x83,
0x34, 0x48, 0xa8, 0x14, 0xed, 0x1c, 0x58, 0x7f, 0x18, 0x49, 0x0c, 0x80, 0xfe, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05,
0x6e, 0x00, 0xcd, 0x75, 0x80, 0x3b, 0xe1, 0x0b, 0x05, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x84, 0xc8, 0xab, 0x35,
0x29, 0x6b, 0xc4, 0x93, 0xcf, 0xd0, 0xe0, 0x53, 0xd6, 0xf5, 0x6b, 0xb9, 0x0e, 0x5d, 0xf0, 0x14, 0xb6, 0x55, 0x3d,
0xbf, 0x0a, 0x39, 0x19, 0xd7, 0x20, 0x2b, 0x24, 0xd3, 0x5f, 0x31, 0x92, 0xf7, 0x5f, 0xf9, 0x55, 0x2d, 0x35, 0x86,
0xb2, 0xf7, 0xeb, 0x9a, 0x61, 0x79, 0x39, 0xaf, 0xdc, 0x14, 0x04, 0xdc, 0x92, 0x25, 0xc1, 0x52, 0x4a, 0x08, 0xd0,
0xb0, 0x3d, 0x92, 0x4a, 0x41, 0x51, 0x6a, 0xf7, 0xce, 0x53, 0xd0, 0x02, 0x8c, 0xa0, 0x96, 0x4a, 0xa6, 0x91, 0xc8,
0x97, 0x42, 0x14, 0x88, 0xf2, 0x60, 0x04, 0x76, 0x6a, 0x33, 0xd2, 0x75, 0xe1, 0xfa, 0xf1, 0x0c, 0x53, 0x7b, 0x08,
0xf4, 0xd8, 0xdb, 0x00, 0x55, 0xa2, 0x2e, 0xc3, 0x72, 0xa2, 0xd0, 0xac, 0x26, 0x59, 0x40, 0x8d, 0x69, 0x83, 0x94,
0x6c, 0x83, 0x2e, 0x57, 0x80, 0x7e, 0x24, 0x8e, 0x67, 0xb5, 0x03, 0x42, 0xd6, 0xa0, 0x82, 0x21, 0x4f, 0xa9, 0x90,
0xc2, 0xbc, 0xd7, 0xa5, 0x22, 0x3c, 0x9f, 0x03, 0x2e, 0xb5, 0xe0, 0xcc, 0xcb, 0x68, 0xe0, 0x83, 0xf8, 0x24, 0xc1,
0xc4, 0x17, 0x5c, 0x15, 0xe8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x28, 0x15, 0x37, 0x29, 0x83, 0x6d, 0x45, 0xae, 0x0d,
0x3f, 0x24, 0xcb, 0xd6, 0x5d, 0x1e, 0xea, 0x2e, 0x44, 0x02, 0xd8, 0xe9, 0x25, 0x7a, 0xbe, 0x65, 0xbd, 0x74, 0x18,
0x9c, 0x69, 0x89, 0x83, 0xc0, 0x6f, 0x6f, 0x27, 0xc3, 0x32, 0x25, 0xb2, 0x6b, 0x92, 0xba, 0x80, 0x1c, 0x86, 0x6a,
0xae, 0x1d, 0x98, 0xa5, 0xd2, 0xc7, 0xf3, 0x72, 0x86, 0xdb, 0xa5, 0x34, 0xe4, 0x66, 0xbc, 0x9a, 0xe6, 0x73, 0x2b,
0xc9, 0xa6, 0xfd, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xd4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59,
0xa7, 0xb8, 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x8c, 0xa6, 0x61, 0x23, 0x60, 0x62, 0x82, 0x8a, 0x5f, 0x37, 0x89, 0x98,
0xce, 0x96, 0xe0, 0x3a, 0x42, 0xef, 0xa1, 0x9c, 0xe0, 0xae, 0xa6, 0xd9, 0xe7, 0xe1, 0xfc, 0x7a, 0xe2, 0xde, 0x37,
0x88, 0xfb, 0xcb, 0x90, 0x1b, 0x84, 0x1e, 0xcb, 0x84, 0x1f, 0xe9, 0xfb, 0x28, 0x54, 0xd5, 0x93, 0xd3, 0xb0, 0x62,
0x59, 0xe2, 0xc9, 0x08, 0x75, 0x18, 0x51, 0xd1, 0x1a, 0x23, 0xbb, 0xba, 0xca, 0xcd, 0xb3, 0x40, 0x4e, 0x53, 0x8f,
0xd7, 0xfd, 0xb4, 0x15, 0x39, 0x1b, 0x9e, 0xc8, 0xfd, 0x57, 0x35, 0x4f, 0x64, 0x45, 0xe7, 0x38, 0xd2, 0x35, 0x81,
0xdc, 0x27, 0xa7, 0xab, 0x87, 0x54, 0xc8, 0x16, 0xbd, 0x6c, 0xe3, 0x8c, 0xea, 0x80, 0xa4, 0x9e, 0x51, 0x81, 0x55,
0x8d, 0xbd, 0xb5, 0xd5, 0x11, 0xe9, 0x96, 0x4a, 0xb0, 0xc1, 0xd6, 0xc2, 0x68, 0xc6, 0x28, 0xe8, 0x94, 0x14, 0x19,
0xa8, 0x51, 0x7e, 0x0d, 0x63, 0xd8, 0xa7, 0x06, 0x20, 0x38, 0xd7, 0x57, 0x7f, 0x59, 0x4a, 0xb2, 0x10, 0x90, 0xb8,
0x4b, 0x06, 0x6c, 0x4d, 0x10, 0x33, 0xd2, 0xc9, 0x7b, 0xa0, 0xbc, 0x01, 0x43, 0x1b, 0x01, 0xec, 0x02, 0x71, 0xe8,
0x41, 0xc5, 0xb6, 0x09, 0x29, 0x3a, 0x36, 0xf0, 0x1c, 0x80, 0x9d, 0x57, 0xae, 0xd1, 0x77, 0x55, 0x0a, 0x18, 0x92,
0x81, 0x1b, 0xb0, 0xca, 0x2d, 0xb7, 0xff, 0x1c, 0xcc, 0x06, 0x78, 0x7d, 0x26, 0x9b, 0x6f, 0x62, 0x9e, 0x60, 0x15,
0xbb, 0xf0, 0x2b, 0xcd, 0x5a, 0xc4, 0x9d, 0x0e, 0x1b, 0xf5, 0x0a, 0x13, 0xa2, 0xf6, 0x00, 0x6b, 0xdf, 0xa3, 0x87,
0x45, 0xbc, 0xbf, 0xc2, 0x77, 0x3d, 0x6e, 0xb9, 0xaf, 0x97, 0x45, 0x2b, 0x5d, 0x45, 0x8d, 0x81, 0xc9, 0xba, 0x9d,
0x8c, 0x6b, 0x2f, 0x0f, 0x84, 0x2f, 0xb8, 0x5a, 0x23, 0xab, 0x5c, 0x8a, 0x8d, 0x45, 0xd2, 0xd3, 0x3e, 0x05, 0xd8,
0x37, 0x9b, 0xbd, 0x00, 0x33, 0xef, 0x2b, 0x54, 0x49, 0x48, 0x69, 0x76, 0x83, 0x25, 0x09, 0x6d, 0x45, 0x46, 0x9d,
0x0f, 0x1c, 0x6d, 0x73, 0x2b, 0x8e, 0x60, 0x38, 0x27, 0x61, 0x3a, 0x56, 0x1e, 0x36, 0x19, 0xb8, 0xf2, 0x8e, 0x98,
0xb6, 0x09, 0xf0, 0x6f, 0x06, 0x7c, 0x7b, 0x25, 0xb9, 0xb6, 0xd0, 0x30, 0x3c, 0x41, 0x84, 0x55, 0x9e, 0x08, 0x34,
0x14, 0x60, 0x8d, 0x6b, 0x2d, 0x0f, 0x50, 0xe1, 0x6b, 0x67, 0x13, 0x00, 0x12, 0x59, 0x41, 0xce, 0x8a, 0xa3, 0x1b,
0x56, 0xb9, 0xde, 0x4f, 0x8d, 0x82, 0xc4, 0xc5, 0x83, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x35, 0x67, 0x52, 0xc4, 0xb4,
0x13, 0x04, 0xfd, 0xa3, 0xcc, 0xc9, 0x69, 0x3a, 0xa1, 0x7d, 0xce, 0x9d, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x89,
0x2d, 0x5e, 0xc7, 0x4d, 0x29, 0x17, 0x26, 0x39, 0xe6, 0xa6, 0x48, 0xc5, 0x66, 0x8a, 0xdd, 0xb9, 0xf5, 0x83, 0x16,
0xd2, 0x41, 0xdb, 0x14, 0x39, 0xd8, 0xac, 0xe2, 0xf7, 0x04, 0xc6, 0x73, 0x81, 0xf8, 0xf2, 0x15, 0x25, 0xe9, 0x30,
0xc7, 0x5c, 0x60, 0xf5, 0x62, 0x0a, 0xf2, 0x77, 0x8e, 0x4e, 0xb3, 0x37, 0xf0, 0x41, 0xe2, 0x0d, 0x38, 0x66, 0x8d,
0x7d, 0xe7, 0x52, 0x51, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0x95, 0xfb, 0x46, 0xd0, 0xd5, 0x5b, 0x1d,
0xce, 0x37, 0x9e, 0x1b, 0xbb, 0x11, 0xc4, 0x60, 0x2d, 0x14, 0x43, 0x4f, 0xb2, 0xf0, 0x1c, 0xb6, 0x67, 0x7b, 0xbb,
0x57, 0xec, 0xf1, 0xca, 0x45, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0x9e, 0x89, 0x9a, 0x58, 0x44, 0x64, 0xcb, 0xd6,
0x61, 0x81, 0x01, 0x00, 0x68, 0x69, 0x72, 0xaf, 0x9a, 0x08, 0x95, 0xf1, 0x5c, 0x5a, 0x4f, 0x15, 0x44, 0x55, 0x8d,
0xdf, 0xae, 0xcf, 0x40, 0x21, 0xb8, 0x37, 0x3a, 0x1e, 0x06, 0x21, 0x60, 0x17, 0x05, 0x2f, 0xd0, 0x07, 0xb4, 0x57,
0x25, 0x42, 0x31, 0x73, 0xb2, 0x1e, 0x33, 0x8c, 0x54, 0xd0, 0x85, 0x4a, 0xd8, 0x2a, 0xcd, 0xf0, 0xab, 0x83, 0xd0,
0x8c, 0x32, 0xee, 0xbf, 0xaa, 0xd6, 0x0c, 0xf2, 0x83, 0x79, 0xab, 0x84, 0xfa, 0x76, 0x25, 0x22, 0x53, 0x81, 0x89,
0x87, 0x59, 0x4a, 0xbf, 0x5f, 0xd6, 0x49, 0x3f, 0x2f, 0x97, 0xe7, 0x9c, 0x24, 0x5f, 0xe7, 0x0e, 0x92, 0x4f, 0xba,
0xfb, 0x95, 0xf0, 0x43, 0x0d, 0xa3, 0x26, 0xfc, 0xea, 0x5b, 0x1a, 0xe6, 0x9e, 0x72, 0x6f, 0xf5, 0xbb, 0xc8, 0x74,
0x51, 0x9e, 0x83, 0x22, 0xa4, 0x1f, 0xc1, 0x34, 0x34, 0x68, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x04, 0x71, 0x7d, 0xcc,
0xa9, 0x76, 0x28, 0x63, 0x8c, 0x68, 0x5a, 0x52, 0x90, 0x24, 0x70, 0x50, 0x7e, 0x03, 0x03, 0x62, 0x12, 0x12, 0xd2,
0x20, 0x74, 0xd6, 0x66, 0x22, 0x2a, 0x73, 0xf1, 0x76, 0xe5, 0xb2, 0x26, 0x50, 0x84, 0x9e, 0x60, 0xa6, 0x52, 0x2a,
0x08, 0xa4, 0xca, 0xb7, 0xd1, 0xa9, 0x39, 0x43, 0x73, 0xd7, 0x14, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0xf2,
0xa1, 0xaf, 0x13, 0x23, 0x5e, 0x66, 0xd0, 0x35, 0x1c, 0xfe, 0x1a, 0x2b, 0x29, 0x42, 0x26, 0x7c, 0xaf, 0x60, 0x13,
0x21, 0x99, 0x82, 0x9e, 0x09, 0xf8, 0x43, 0xbd, 0xb2, 0x97, 0xee, 0xe5, 0x95, 0x49, 0x8b, 0xca, 0x56, 0xa2, 0x66,
0x2d, 0x8e, 0xe2, 0xed, 0x14, 0xce, 0xb3, 0x47, 0x09, 0x04, 0x24, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x28, 0x1d, 0x02,
0x48, 0x70, 0xfa, 0x09, 0x2c, 0xb4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0x90, 0x9a, 0x73, 0x92, 0x7c,
0x73, 0x94, 0xda, 0xdb, 0x4a, 0x7b, 0xc6, 0xec, 0x00, 0xdb, 0x76, 0xb7, 0xf3, 0xa3, 0x74, 0xbb, 0x33, 0x34, 0x18,
0x17, 0x86, 0xff, 0x93, 0x12, 0xd3, 0x40, 0x0a, 0x29, 0x1b, 0x3f, 0xa1, 0x0c, 0xc3, 0xff, 0x96, 0x24, 0x80, 0x07,
0xb5, 0xdd, 0x58, 0x31, 0xee, 0x15, 0x45, 0xc9, 0x6d, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0xf4, 0x89, 0x62,
0x9e, 0x13, 0x00, 0xa3, 0xc8, 0xfc, 0x1d, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f,
0x29, 0xa4, 0xa2, 0xb2, 0x39, 0x89, 0xf8, 0x77, 0x05, 0x98, 0xe6, 0xc4, 0x47, 0x7a, 0xae, 0x61, 0x28, 0xc0, 0x57,
0x3a, 0x94, 0x9a, 0xed, 0xe9, 0x1f, 0x9d, 0xed, 0xbe, 0x44, 0x8a, 0x20, 0x81, 0x06, 0x5e, 0xae, 0x59, 0x2f, 0xac,
0x32, 0xb8, 0x23, 0xfe, 0x14, 0x7c, 0x5f, 0x5e, 0x07, 0x9f, 0x71, 0xfe, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x83,
0xa6, 0x62, 0x25, 0xd8, 0x25, 0x0a, 0xcc, 0xca, 0xcf, 0x1f, 0xd7, 0x69, 0xdd, 0xd4, 0x2c, 0xd1, 0x29, 0x3f, 0x77,
0x0d, 0x33, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, 0x7f, 0x0e, 0xb2, 0x9d, 0x50, 0xbb, 0xb5, 0x55, 0x6c, 0x90, 0x86, 0x86,
0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xf1, 0x52, 0xa8, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x87,
0x15, 0x82, 0xc5, 0x4e, 0x65, 0xf2, 0x19, 0x0e, 0x9a, 0x22, 0xd7, 0x42, 0x28, 0x7c, 0x39, 0x88, 0x4a, 0x49, 0x8b,
0x75, 0xb4, 0x3d, 0x3b, 0x83, 0xe7, 0x97, 0x71, 0x01, 0xd8, 0x81, 0xe5, 0x57, 0x58, 0x16, 0x07, 0xc8, 0xc5, 0x43,
0x59, 0xeb, 0x15, 0x8d, 0xc7, 0x37, 0x76, 0x61, 0x75, 0x01, 0x3e, 0x8d, 0xd2, 0x71, 0x22, 0x26, 0x31, 0x93, 0x2a,
0xd7, 0xe4, 0xda, 0xe8, 0x5e, 0x5a, 0xa3, 0x79, 0x2e, 0x38, 0x78, 0x85, 0xe0, 0x06, 0xd3, 0x57, 0xf2, 0x72, 0xbd,
0x82, 0x82, 0xa1, 0xf6, 0xe6, 0x26, 0x98, 0x2b, 0xf1, 0x98, 0xc1, 0x35, 0xfd, 0x3a, 0x9c, 0x8a, 0x6e, 0x5e, 0xae,
0x18, 0xfc, 0x3a, 0x67, 0xac, 0x21, 0x00, 0x88, 0x4e, 0x1e, 0x5e, 0x6f, 0x26, 0xbd, 0x52, 0xd2, 0x41, 0x49, 0x84,
0xf8, 0xae, 0xcc, 0xd7, 0x5d, 0x2a, 0xba, 0x72, 0xd5, 0xbd, 0xaf, 0x19, 0x33, 0x2e, 0x18, 0x3d, 0xe7, 0xb3, 0xa4,
0x71, 0xed, 0x86, 0xee, 0xea, 0xfc, 0xe8, 0xfd, 0x20, 0xf3, 0x16, 0x66, 0x40, 0x26, 0x20, 0x0a, 0x9e, 0x7b, 0xaf,
0x8d, 0x88, 0xf2, 0xb7, 0x66, 0x88, 0x57, 0x0e, 0xb3, 0x2e, 0x92, 0xfc, 0xed, 0xe0, 0xdb, 0xe0, 0xfa, 0x96, 0x46,
0x04, 0xb9, 0xab, 0x22, 0xc8, 0x84, 0xb9, 0x99, 0x3e, 0x70, 0xfb, 0x77, 0x65, 0x08, 0x22, 0x2a, 0xa6, 0x43, 0xe5,
0xb8, 0x7f, 0xb4, 0x41, 0xa5, 0x42, 0xe2, 0x53, 0x95, 0xbb, 0x72, 0x6d, 0x6a, 0xa8, 0xc7, 0x75, 0x32, 0x0b, 0x4d,
0xb3, 0x26, 0x97, 0xb2, 0x69, 0x31, 0x32, 0x4d, 0x4e, 0xb5, 0xf9, 0xdd, 0x6b, 0x83, 0x74, 0x0c, 0xd5, 0xc5, 0x5a,
0x2d, 0x98, 0xdf, 0x95, 0x17, 0xde, 0xf5, 0x62, 0x23, 0x95, 0xa1, 0xa6, 0x3d, 0x8a, 0x3e, 0x8e, 0xdb, 0xcc, 0xe5,
0x51, 0xfa, 0x67, 0x0d, 0x00, 0xd3, 0x10, 0x16, 0xdd, 0x4d, 0xcb, 0xd8, 0x13, 0xcb, 0xd3, 0x13, 0x19, 0x28, 0x7a,
0xae, 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x35, 0x08, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb8, 0x5b, 0xad, 0x5f, 0xcd, 0xf3,
0x79, 0xca, 0x57, 0xf2, 0x7c, 0x6a, 0x1a, 0xdd, 0x46, 0xdb, 0xbd, 0x39, 0x35, 0x54, 0xcc, 0xb5, 0xbe, 0xc9, 0x1f,
0x98, 0xae, 0x83, 0xae, 0x16, 0x81, 0x66, 0x75, 0xaa, 0x9e, 0x95, 0xe5, 0xac, 0x9e, 0xc9, 0x31, 0x13, 0xb6, 0xa9,
0x34, 0x87, 0xe8, 0x86, 0xa9, 0x9a, 0xe9, 0xc7, 0xc6, 0xb1, 0x90, 0x6d, 0x9e, 0x5f, 0x8e, 0x73, 0xc0, 0xb4, 0x3c,
0x5f, 0x26, 0x0c, 0x3f, 0x5e, 0x5d, 0xfd, 0x28, 0xf8, 0x54, 0xd5, 0xd1, 0x5b, 0xbe, 0xd4, 0x3d, 0x83, 0x59, 0xa9,
0x8c, 0x88, 0x13, 0xb6, 0x7e, 0xf0, 0xe6, 0xe9, 0x15, 0xb0, 0x9c, 0xc0, 0xea, 0x4e, 0x98, 0xd3, 0x18, 0xaa, 0x3a,
0xc0, 0x3f, 0xac, 0x1f, 0x6c, 0xdd, 0x19, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x63, 0x63, 0xe3, 0x18, 0xef, 0xd6, 0x12,
0x41, 0x5e, 0x61, 0x40, 0x1f, 0xaf, 0x3e, 0x0a, 0x5c, 0xae, 0x63, 0xdb, 0x03, 0x87, 0xdc, 0xd6, 0xc0, 0xdf, 0x24,
0x4f, 0x1a, 0x2d, 0x0a, 0x9e, 0xcd, 0xe4, 0x0c, 0x85, 0xbc, 0xe6, 0xe3, 0xa0, 0xee, 0x08, 0x7f, 0x03, 0xa7, 0x16,
0x5e, 0x5e, 0x7e, 0x82, 0x3e, 0x60, 0xe9, 0x4a, 0x6e, 0x2a, 0xfc, 0x94, 0xf2, 0x88, 0xae, 0xd6, 0x79, 0x30, 0x52,
0x5c, 0x4c, 0x51, 0xe8, 0xb8, 0xcb, 0x1b, 0x67, 0x23, 0xa3, 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab,
0x96, 0xde, 0x2f, 0x3a, 0xba, 0x6d, 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x0a, 0xcc, 0xf8, 0x38, 0x11, 0x5e, 0x9f, 0x18,
0xeb, 0xbb, 0x45, 0xa0, 0xba, 0x39, 0x36, 0xd9, 0xe1, 0x78, 0xbd, 0xd9, 0xac, 0x71, 0x07, 0x6f, 0x9c, 0x27, 0xce,
0xb2, 0x44, 0x8f, 0xca, 0x52, 0xc3, 0x03, 0x52, 0x21, 0x6e, 0xde, 0x33, 0x81, 0x71, 0xd9, 0x25, 0x71, 0x6d, 0x37,
0x10, 0x6b, 0xb1, 0x27, 0x31, 0x4b, 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x7b, 0xed,
0xec, 0x3c, 0xb5, 0xbd, 0x6a, 0xaa, 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x14, 0x49, 0xf7, 0x1b, 0xe8, 0x22, 0x06, 0x8c,
0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, 0x8e, 0xf8, 0x46, 0x13, 0xa9, 0x52, 0x1f, 0x51, 0xdf, 0x61, 0x58, 0xab, 0xab,
0x0c, 0x24, 0x30, 0x8f, 0xbc, 0x3b, 0xae, 0xa5, 0xa7, 0x63, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0xba,
0xbd, 0xe2, 0x01, 0xe8, 0x1f, 0xe8, 0xb7, 0x88, 0x85, 0xbf, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xe4, 0xe8, 0x6b,
0x0f, 0x7e, 0x61, 0xd5, 0x9e, 0x81, 0x1a, 0x66, 0xc4, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0b, 0xc0,
0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, 0x64, 0xb9, 0x2a, 0x86, 0xfc, 0x1b, 0xec, 0xb3, 0xde, 0x9c, 0x80, 0x99, 0x38,
0xe5, 0x25, 0x26, 0xa6, 0x88, 0xcb, 0xcd, 0xd2, 0xe7, 0x69, 0xda, 0x2c, 0xda, 0xc0, 0x29, 0x8c, 0x04, 0x8e, 0xd8,
0x37, 0xb6, 0xa1, 0x99, 0xb0, 0x11, 0x13, 0x6a, 0x54, 0x4a, 0x09, 0x1f, 0xc8, 0xad, 0x96, 0xf4, 0x65, 0x6e, 0xaf,
0xbe, 0xdc, 0x26, 0x28, 0xa0, 0xa8, 0x81, 0xe5, 0xd0, 0x38, 0x6e, 0x19, 0xc8, 0x85, 0xc5, 0xb0, 0x30, 0x6a, 0x55,
0xae, 0x26, 0xa3, 0x3a, 0x99, 0xaf, 0x16, 0x17, 0x2a, 0xf4, 0xe0, 0x91, 0x40, 0xce, 0x5f, 0x60, 0xea, 0x60, 0x56,
0x6a, 0x33, 0x2d, 0x36, 0x51, 0xde, 0x33, 0x1d, 0x92, 0xeb, 0xaf, 0xe1, 0xa1, 0xf2, 0x8b, 0x57, 0xe6, 0x14, 0xf3,
0x45, 0x1e, 0x4b, 0x5b, 0x63, 0x6e, 0xfd, 0xaf, 0xf2, 0x3e, 0xad, 0x04, 0xec, 0x37, 0x60, 0x53, 0xc6, 0x5a, 0x62,
0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xf4, 0xbe, 0x86, 0xf0, 0x5d, 0x51, 0xe9, 0x2a, 0x91, 0x75, 0x8d, 0x56, 0xf7,
0xeb, 0x82, 0xe5, 0x97, 0x07, 0x0c, 0x73, 0x93, 0x51, 0x21, 0x5b, 0x51, 0xb3, 0x29, 0xbf, 0xda, 0xbb, 0xf1, 0x2b,
0x0f, 0x25, 0x05, 0xd5, 0x2a, 0xd9, 0xbc, 0x72, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x8c, 0x7b, 0x14, 0x57, 0xae, 0x50,
0xad, 0xf3, 0xdf, 0x57, 0xdd, 0x4f, 0x74, 0xd6, 0x86, 0xfa, 0xd4, 0x0d, 0xd7, 0xa6, 0xa7, 0xdf, 0xb0, 0x54, 0x23,
0x4b, 0xe8, 0xa6, 0xa5, 0x62, 0x32, 0x12, 0x25, 0xa6, 0xab, 0x94, 0x47, 0x7d, 0x8d, 0xb8, 0x00, 0x76, 0x43, 0xf9,
0x8b, 0x7f, 0x0d, 0xcf, 0x8f, 0x03, 0x54, 0xa2, 0x96, 0x93, 0x2c, 0xe5, 0xad, 0x49, 0x34, 0x8b, 0x93, 0xcb, 0x60,
0x11, 0xb7, 0x66, 0x59, 0x9a, 0x15, 0x73, 0xa0, 0x4a, 0xaf, 0xb8, 0x04, 0x1d, 0x7e, 0xd6, 0x5a, 0xc4, 0xde, 0x73,
0x96, 0x9c, 0x31, 0x1e, 0x8f, 0x22, 0xcf, 0xde, 0xcf, 0x81, 0x3d, 0x58, 0xaf, 0xa3, 0x3c, 0xcf, 0xce, 0x6d, 0xef,
0x5d, 0x76, 0x02, 0x44, 0xeb, 0xbd, 0xb9, 0xb8, 0x3c, 0x65, 0xa9, 0xf7, 0xfe, 0x64, 0x91, 0xf2, 0x85, 0x57, 0x44,
0x69, 0xd1, 0x2a, 0x58, 0x1e, 0x4f, 0x40, 0x4c, 0x24, 0x59, 0xde, 0xc2, 0xfc, 0xe7, 0x19, 0x0b, 0x92, 0xf8, 0x74,
0xca, 0xad, 0x71, 0x94, 0x7f, 0xea, 0xb5, 0x5a, 0xf3, 0x3c, 0x9e, 0x45, 0xf9, 0x65, 0x8b, 0x5a, 0x04, 0x9f, 0xb5,
0x77, 0xa3, 0x2f, 0x26, 0xf7, 0x7b, 0x3c, 0x87, 0xbe, 0x31, 0x62, 0x31, 0x00, 0xe6, 0x63, 0xed, 0x3e, 0x68, 0xcf,
0x8a, 0x0d, 0x11, 0x51, 0x8a, 0x52, 0x5e, 0x1e, 0x7b, 0x1f, 0x41, 0xb7, 0x3d, 0xf6, 0x4f, 0x78, 0xea, 0x81, 0x2d,
0xc7, 0xb3, 0x74, 0x39, 0x5a, 0xe4, 0x05, 0x0c, 0x30, 0xcf, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x92, 0xe5, 0x80, 0xb6,
0x56, 0x1e, 0x8d, 0xe3, 0x45, 0x11, 0xdc, 0x9f, 0x5f, 0xf4, 0x50, 0x57, 0x38, 0xcd, 0xb3, 0x45, 0x3a, 0x96, 0x73,
0xc5, 0x29, 0x1c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc5, 0xce, 0xa8,
0xe8, 0xb7, 0xc7, 0xec, 0xd4, 0xcb, 0x4f, 0x4f, 0x22, 0xa7, 0xd3, 0x7d, 0xe4, 0xa9, 0x7f, 0xfe, 0x03, 0x17, 0x14,
0xf7, 0xb5, 0xc5, 0x9d, 0x76, 0xfb, 0x1f, 0xdc, 0x5e, 0x63, 0x16, 0x02, 0x28, 0xe8, 0xcc, 0x2f, 0xac, 0x22, 0x4b,
0x60, 0x7f, 0xd6, 0xf5, 0xec, 0xcd, 0xc1, 0x6e, 0x8a, 0xd3, 0xd3, 0xa0, 0x3b, 0xbf, 0x28, 0x71, 0x75, 0x81, 0x48,
0xc8, 0x94, 0x8b, 0x94, 0x6f, 0xcb, 0x3f, 0x0a, 0xf1, 0xe3, 0xf5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd6, 0x5b, 0x63,
0x38, 0x07, 0x84, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x82, 0x11, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d,
0x0f, 0xc6, 0xe3, 0xdb, 0xc0, 0xc8, 0xd3, 0xf1, 0xb2, 0xbe, 0xaf, 0x1d, 0x30, 0x4e, 0x7b, 0x53, 0x86, 0xf4, 0x14,
0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf3, 0xa9, 0x78, 0x24, 0x72, 0x3e, 0x17, 0x75, 0x0f, 0xda, 0x6d, 0xf1, 0x5e, 0x80,
0x40, 0x0b, 0x3a, 0x3e, 0x36, 0x00, 0x22, 0x7a, 0x71, 0xdd, 0x47, 0x6c, 0x3e, 0xdc, 0xfa, 0xa5, 0x1a, 0xef, 0x52,
0xe5, 0x0d, 0x0a, 0x11, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0x45, 0xbf, 0xa3, 0x03, 0x55, 0x83, 0x0f, 0x8c, 0xa4,
0x5e, 0xc0, 0x3d, 0x33, 0x17, 0xa8, 0x97, 0xf6, 0xd1, 0x25, 0xd5, 0x6a, 0xb9, 0x20, 0x37, 0x18, 0xba, 0x90, 0x28,
0x20, 0xe8, 0x14, 0x83, 0x9c, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xee, 0x64, 0x2e, 0x1c, 0xf9, 0x4c, 0xf3, 0xf5, 0x62,
0x6b, 0x0b, 0xac, 0xec, 0x17, 0x4c, 0x36, 0x00, 0xee, 0x4d, 0xae, 0xae, 0xef, 0x43, 0x61, 0x4a, 0x29, 0x43, 0x6a,
0x76, 0xd3, 0x15, 0x7d, 0xd8, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x86, 0xff, 0x0e, 0xc5, 0xde, 0xd1, 0x86, 0x65, 0x91,
0x2d, 0xf2, 0x11, 0x79, 0xea, 0x56, 0x2d, 0x7e, 0x9b, 0x04, 0xae, 0xed, 0x31, 0xcd, 0xe7, 0xd1, 0x0c, 0xae, 0x7d,
0xe4, 0x80, 0x53, 0x10, 0x44, 0xdc, 0x31, 0x90, 0x5e, 0x0e, 0x05, 0x21, 0x8a, 0xae, 0x31, 0xe5, 0xbb, 0xd1, 0xfd,
0x4b, 0x7f, 0x91, 0xc6, 0xc0, 0xe9, 0x3e, 0xc6, 0x63, 0xba, 0x77, 0x12, 0x8f, 0x29, 0x10, 0xd1, 0xa2, 0xc4, 0x23,
0xf4, 0x6c, 0x43, 0x81, 0xfa, 0x0e, 0x0b, 0x3c, 0xcb, 0x44, 0x16, 0xbb, 0x65, 0x63, 0x30, 0xc1, 0x10, 0x95, 0xe3,
0x6c, 0x16, 0xc5, 0x69, 0x80, 0xdf, 0x07, 0xf1, 0xf4, 0x88, 0x01, 0x76, 0xf1, 0xe0, 0x27, 0x93, 0xb9, 0x68, 0x1d,
0xd7, 0xff, 0x05, 0xf8, 0x08, 0xf5, 0x2f, 0xa5, 0x1d, 0xa6, 0xe1, 0x52, 0x61, 0xde, 0x7a, 0x29, 0xf0, 0x1e, 0xae,
0x74, 0x56, 0x46, 0x7e, 0x8e, 0x3d, 0x4e, 0x3f, 0x06, 0xad, 0x4e, 0xd0, 0xd1, 0xa6, 0x6b, 0xed, 0x36, 0xaa, 0xc8,
0x65, 0x91, 0x37, 0x1a, 0x09, 0x06, 0xfd, 0x2c, 0xe0, 0xac, 0xde, 0x35, 0xac, 0x9e, 0xa4, 0x4b, 0x74, 0xe0, 0x9c,
0xa6, 0x4e, 0x0d, 0x08, 0x8a, 0x05, 0x5c, 0x33, 0x95, 0x5b, 0x46, 0x24, 0x94, 0xbe, 0xa4, 0x03, 0x5c, 0xbf, 0x4b,
0x84, 0xf7, 0x86, 0xea, 0x29, 0x50, 0x8a, 0xe4, 0x16, 0xc7, 0x7b, 0xe2, 0xc4, 0x5b, 0x44, 0x63, 0xa1, 0x0d, 0x47,
0xd0, 0xb6, 0xfe, 0x32, 0x02, 0x2c, 0x7d, 0x0a, 0xed, 0xcd, 0xa5, 0xa3, 0x12, 0xeb, 0x73, 0x98, 0x6b, 0x5f, 0x48,
0x3d, 0xba, 0x91, 0x6f, 0xf7, 0x37, 0x97, 0xbc, 0xdc, 0xdb, 0x11, 0xbd, 0xfb, 0xc7, 0x65, 0x41, 0x02, 0xca, 0x74,
0xa4, 0x55, 0x53, 0x88, 0x3a, 0x18, 0x96, 0xd2, 0x77, 0x71, 0xdc, 0x42, 0x2b, 0x5d, 0xc2, 0x63, 0x2c, 0xc9, 0x2e,
0xc7, 0x74, 0xa5, 0x28, 0x87, 0x33, 0xa9, 0x13, 0x52, 0x72, 0x91, 0x83, 0xd1, 0x5b, 0x85, 0xe2, 0x18, 0x21, 0x18,
0x6c, 0x2e, 0xe3, 0x32, 0xdc, 0x5c, 0x66, 0xe5, 0x31, 0x68, 0x26, 0x08, 0x55, 0xa1, 0x3e, 0xef, 0x02, 0x13, 0x0b,
0x27, 0x8b, 0x45, 0x23, 0xe0, 0xb4, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x06, 0x2c, 0x40, 0x2c, 0x00, 0xdd, 0x8d, 0x7a,
0x31, 0x58, 0x8b, 0x68, 0xdd, 0x87, 0x81, 0xf6, 0x76, 0x44, 0x23, 0x58, 0x57, 0x8e, 0x20, 0x57, 0xcb, 0xc2, 0x74,
0x1c, 0x73, 0x69, 0x49, 0x74, 0xc2, 0x12, 0xe8, 0x9f, 0x5f, 0x5d, 0xb5, 0xa1, 0x9b, 0x78, 0xb5, 0xf6, 0xe2, 0x74,
0xbe, 0x90, 0xdf, 0xd4, 0x82, 0x59, 0x3a, 0x18, 0xe6, 0xc4, 0x94, 0xff, 0x81, 0x8a, 0xdb, 0x05, 0x36, 0x8d, 0x6b,
0x03, 0x3c, 0x14, 0x32, 0x40, 0x50, 0x2a, 0x1a, 0x80, 0xd2, 0x78, 0xbc, 0x5a, 0xa6, 0x97, 0x51, 0xc0, 0x0b, 0x9c,
0xc1, 0x39, 0x3e, 0xa7, 0xf0, 0x3c, 0x8b, 0x53, 0x7c, 0xcc, 0xf1, 0x31, 0xba, 0xc0, 0xc7, 0xac, 0xb4, 0xff, 0x2e,
0xe8, 0xb6, 0x34, 0x02, 0xb2, 0xab, 0x2b, 0x60, 0xee, 0x1a, 0x05, 0x40, 0x10, 0xe2, 0xdb, 0x2a, 0xcc, 0xc4, 0x16,
0x2b, 0xe6, 0x2d, 0x51, 0x6e, 0x91, 0xf0, 0x0c, 0xc1, 0xb6, 0xca, 0x9d, 0x86, 0x8e, 0xe0, 0xc9, 0x2c, 0x92, 0x27,
0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xf8, 0x85, 0x40, 0x07, 0x3d, 0xe2, 0xda, 0x74, 0x19, 0x97, 0x9f, 0xb5, 0x89, 0x43,
0x1b, 0x67, 0x01, 0x35, 0x0d, 0x99, 0x3d, 0x8f, 0xe2, 0x44, 0x34, 0x5e, 0xb3, 0x92, 0x46, 0x3a, 0x20, 0x2d, 0x64,
0x6f, 0xa7, 0x82, 0x0d, 0x80, 0x1f, 0x89, 0xcb, 0xd4, 0x15, 0xf4, 0xb6, 0xa8, 0xa2, 0x28, 0xb9, 0x3c, 0xbc, 0x03,
0xe1, 0x0f, 0xd7, 0xeb, 0x1c, 0x82, 0x5d, 0x17, 0xa5, 0xf5, 0x16, 0x00, 0xf1, 0x9c, 0xb1, 0xb1, 0x67, 0x5b, 0xc0,
0x26, 0xc5, 0xf3, 0xc7, 0x84, 0x9d, 0x31, 0xf9, 0x11, 0x14, 0xdd, 0x57, 0x57, 0x8e, 0x40, 0xda, 0x72, 0x79, 0x3f,
0x53, 0x52, 0x9e, 0x5a, 0x97, 0x5c, 0x7d, 0x1d, 0x78, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x75,
0x40, 0x91, 0xb5, 0x01, 0x4c, 0xd2, 0xcf, 0x6e, 0x5a, 0x0a, 0xf4, 0x63, 0x93, 0x09, 0x1c, 0x00, 0x15, 0xb7, 0xd0,
0xa7, 0x5b, 0x00, 0x03, 0x66, 0xa6, 0x67, 0x8b, 0x16, 0x76, 0xd5, 0x56, 0x3f, 0x21, 0x2a, 0x92, 0x6c, 0xf4, 0xa9,
0x36, 0xc5, 0x02, 0x09, 0x08, 0xc7, 0x6a, 0xf0, 0x29, 0xfb, 0xdf, 0xfe, 0xf5, 0x7f, 0xfe, 0x57, 0x18, 0x8e, 0x3a,
0xb8, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x01, 0xad, 0x16, 0xe9, 0x2d, 0xed, 0xfe, 0xf6, 0xcf, 0xff, 0x0d, 0x9a, 0xd1,
0xcd, 0x1a, 0xb7, 0x3c, 0x0e, 0xec, 0x11, 0x6a, 0x32, 0x77, 0x03, 0xa4, 0xd6, 0xf5, 0xda, 0xf1, 0xff, 0x05, 0x81,
0x2d, 0x78, 0x36, 0xbf, 0x11, 0x08, 0x84, 0x75, 0x94, 0x64, 0x05, 0x13, 0x50, 0x08, 0x36, 0x79, 0x47, 0x30, 0x68,
0x86, 0x39, 0x90, 0x6c, 0x61, 0x89, 0xde, 0x02, 0xfb, 0xb5, 0xde, 0x8d, 0x5d, 0x29, 0x18, 0x27, 0xd0, 0xc9, 0x03,
0x00, 0xfb, 0x20, 0x9e, 0xe0, 0x81, 0x4e, 0x33, 0x6c, 0xbb, 0xce, 0x17, 0x68, 0x0c, 0xa1, 0x89, 0x4c, 0x8c, 0x20,
0x5c, 0x1d, 0xaa, 0x1f, 0xfc, 0x04, 0xd6, 0xf2, 0x51, 0x3f, 0x47, 0x17, 0xfa, 0x19, 0xd9, 0x0f, 0x0c, 0x0b, 0x82,
0x62, 0x86, 0x3a, 0x40, 0x73, 0x61, 0xea, 0xa4, 0x56, 0xfc, 0x81, 0xa9, 0xe4, 0xb0, 0x8f, 0x98, 0x0f, 0x89, 0xb7,
0x5f, 0x16, 0x39, 0xab, 0x38, 0x26, 0x36, 0x10, 0xac, 0xc8, 0xac, 0xff, 0x98, 0x64, 0xe7, 0xe5, 0x75, 0x75, 0x53,
0xa0, 0xe2, 0x72, 0x6f, 0x1c, 0x9f, 0xf5, 0x25, 0x22, 0x1b, 0x6b, 0x59, 0xed, 0xd2, 0x5c, 0x18, 0x56, 0xc9, 0x75,
0xc9, 0x47, 0x5c, 0x96, 0xd7, 0x46, 0x01, 0x80, 0xe3, 0xee, 0x9d, 0xe4, 0x7d, 0xb9, 0x80, 0x57, 0x78, 0x61, 0x8b,
0x20, 0x41, 0x3e, 0x2e, 0x64, 0x0c, 0x27, 0x19, 0x63, 0xb2, 0x7a, 0xd4, 0x5a, 0x33, 0xc5, 0xd2, 0xb1, 0x61, 0x8d,
0x0b, 0x73, 0xc9, 0x85, 0x63, 0xa9, 0x0e, 0x49, 0x2e, 0x8c, 0x1f, 0xe0, 0x68, 0x70, 0xe1, 0xf8, 0x5a, 0x2e, 0x8c,
0x6b, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x8d, 0xb6, 0xb8, 0x21, 0x15, 0x38, 0x0a, 0x37, 0x30, 0xc0, 0x46, 0x9f, 0xa4,
0x6c, 0x23, 0x50, 0xd1, 0x18, 0x95, 0xd2, 0x9a, 0x04, 0x9b, 0x64, 0xcf, 0xc1, 0xe2, 0x18, 0x64, 0x9b, 0x39, 0x32,
0x58, 0xc2, 0x13, 0x86, 0xc7, 0xff, 0x78, 0x07, 0xfb, 0x8a, 0xcd, 0x2c, 0xe9, 0x19, 0xa4, 0xcf, 0x0e, 0x0d, 0xe0,
0x2d, 0x85, 0x3b, 0x23, 0xb0, 0xdf, 0xbe, 0x39, 0x38, 0xb4, 0xbd, 0x93, 0x6c, 0x7c, 0x19, 0xd8, 0xa0, 0x8a, 0x82,
0x24, 0x73, 0x7d, 0x3e, 0x65, 0xa9, 0xa3, 0x94, 0xc1, 0x2c, 0x01, 0x65, 0x38, 0x3b, 0x15, 0xb7, 0xef, 0x9b, 0xae,
0x58, 0x40, 0x1b, 0x7d, 0x9e, 0xaf, 0xbf, 0xc7, 0xc5, 0x97, 0x2b, 0x79, 0x8e, 0x8f, 0x7d, 0x0c, 0x46, 0xef, 0xed,
0xc0, 0x03, 0xbe, 0x1c, 0x20, 0x05, 0xe9, 0x37, 0x01, 0x67, 0x21, 0xde, 0x77, 0xb0, 0xfd, 0x8e, 0xea, 0x8b, 0x50,
0x28, 0x1a, 0xd0, 0xfa, 0x5a, 0xa5, 0x04, 0xd0, 0xd8, 0x63, 0x22, 0x41, 0xdc, 0x18, 0xc0, 0x01, 0x1f, 0xeb, 0x12,
0x41, 0xa6, 0x46, 0x11, 0x8d, 0x52, 0xb1, 0x7f, 0x59, 0x85, 0x13, 0x12, 0xfa, 0xc4, 0x64, 0xf0, 0x93, 0xc0, 0x3f,
0x36, 0xbf, 0x34, 0x25, 0x3e, 0x0a, 0xa3, 0x17, 0x79, 0xf4, 0x57, 0xb0, 0x61, 0xbd, 0xf3, 0x63, 0x6a, 0xa9, 0xcc,
0x1a, 0xb4, 0xb7, 0xd1, 0xfc, 0x6b, 0x2b, 0xfb, 0x15, 0x24, 0x5e, 0x12, 0xcd, 0x0b, 0x16, 0xa8, 0x07, 0x69, 0xe1,
0xa0, 0xa1, 0xb4, 0x6a, 0x52, 0x9a, 0x92, 0xb1, 0xe4, 0xd3, 0xa5, 0x69, 0x02, 0x3d, 0x04, 0x13, 0x08, 0xd3, 0xb7,
0x5b, 0x11, 0xb0, 0xf7, 0x34, 0x48, 0xd8, 0x84, 0x97, 0x1c, 0xef, 0x07, 0x2f, 0x95, 0xcd, 0xe9, 0x77, 0x1f, 0x80,
0x59, 0x64, 0xf9, 0xf8, 0xff, 0x6d, 0x63, 0x8f, 0x83, 0x14, 0xcc, 0x18, 0xba, 0x30, 0x80, 0x97, 0xb1, 0x00, 0x22,
0xf3, 0x7d, 0x69, 0x4c, 0x34, 0x62, 0x68, 0x8f, 0x97, 0x3c, 0xb7, 0xf8, 0xd4, 0xe3, 0xb9, 0xd9, 0x0e, 0x34, 0xa5,
0x15, 0xa3, 0x7c, 0xd5, 0x2c, 0xdc, 0x75, 0xa5, 0xf2, 0xb8, 0xda, 0x58, 0xd9, 0xd6, 0xf5, 0xb7, 0x15, 0x0c, 0x19,
0x5e, 0x80, 0x52, 0x70, 0xbe, 0xa5, 0xe8, 0x61, 0xae, 0x69, 0xd5, 0x3f, 0x70, 0xab, 0xee, 0x51, 0xd2, 0xd9, 0x3e,
0xa2, 0xb3, 0x4d, 0xcc, 0x65, 0xb8, 0x14, 0x73, 0x8f, 0xa2, 0x64, 0xe4, 0x20, 0x00, 0x56, 0xcb, 0xba, 0x0f, 0xd8,
0x04, 0x2e, 0x3d, 0x2c, 0xcb, 0xde, 0x25, 0x73, 0x8e, 0x7e, 0x93, 0x79, 0xe4, 0xe2, 0xfa, 0xa0, 0xfe, 0x04, 0x5b,
0xbb, 0x74, 0x87, 0xde, 0xf7, 0xc6, 0x77, 0xad, 0x6c, 0x45, 0xa9, 0xb6, 0x07, 0xf8, 0xfd, 0x3e, 0xc4, 0xbe, 0xaf,
0x1c, 0x1b, 0xb5, 0x10, 0xaa, 0xb9, 0x6c, 0x11, 0xe1, 0xd8, 0xd8, 0x4d, 0x78, 0x41, 0xbf, 0xba, 0xce, 0x98, 0xfd,
0xee, 0x76, 0x63, 0x96, 0xdd, 0xd1, 0x98, 0xfd, 0xee, 0x4f, 0x36, 0x66, 0xbf, 0x6a, 0x1a, 0xb3, 0xbf, 0xfe, 0x1e,
0x63, 0x36, 0xcf, 0xce, 0x8b, 0xb0, 0x23, 0x83, 0xa7, 0xc0, 0x4c, 0xfe, 0x3e, 0x56, 0x2d, 0x4c, 0xd4, 0xb0, 0x69,
0xc9, 0x88, 0x15, 0xf9, 0x5e, 0xc0, 0xab, 0xa5, 0x09, 0xd9, 0xd6, 0x89, 0x55, 0xad, 0xfb, 0xea, 0x26, 0x09, 0xe8,
0xf5, 0xae, 0xbe, 0x03, 0xd5, 0x55, 0x46, 0x66, 0x40, 0x9f, 0x82, 0xd4, 0x1d, 0xbb, 0xdb, 0x2a, 0xa3, 0xc7, 0x1c,
0xa1, 0xa7, 0x1c, 0xb5, 0x82, 0x7c, 0x96, 0xf6, 0x7f, 0x3a, 0xea, 0xf4, 0x76, 0x3b, 0x33, 0xe8, 0x0d, 0x72, 0x0b,
0xde, 0xda, 0xbd, 0xdd, 0x5d, 0x7c, 0x3b, 0x57, 0x6f, 0x5d, 0x7c, 0x8b, 0xd5, 0xdb, 0x03, 0x7c, 0x1b, 0xa9, 0xb7,
0x87, 0xf8, 0x36, 0x56, 0x6f, 0x8f, 0xf0, 0xed, 0xcc, 0x2e, 0x8f, 0xb8, 0x06, 0xee, 0x11, 0xd0, 0x15, 0x29, 0x89,
0x81, 0x2a, 0x83, 0xd3, 0x88, 0x37, 0xb0, 0xa2, 0xd3, 0x20, 0xf6, 0x84, 0x02, 0x1d, 0x14, 0xde, 0x39, 0xb0, 0xf4,
0x80, 0x12, 0x8e, 0x9e, 0xe2, 0x55, 0x7c, 0xd0, 0x3d, 0x0f, 0xe3, 0x19, 0x53, 0xdf, 0x24, 0x55, 0xab, 0x06, 0x35,
0x05, 0xec, 0xed, 0xb2, 0xa7, 0xf7, 0x49, 0xd8, 0xd0, 0x2a, 0x77, 0x82, 0x76, 0xae, 0xaa, 0x13, 0xd3, 0xb5, 0xf4,
0x0e, 0x5f, 0x23, 0x20, 0x40, 0x00, 0x2b, 0xa3, 0x74, 0x02, 0x6a, 0x40, 0xeb, 0x02, 0x94, 0xf4, 0xb5, 0x42, 0x03,
0x21, 0xd2, 0x62, 0x82, 0xd6, 0xa4, 0xdf, 0x0e, 0xa3, 0x53, 0xfd, 0xfc, 0x0a, 0xf4, 0xa9, 0xe8, 0x94, 0xdd, 0x26,
0x40, 0x08, 0x44, 0x53, 0x78, 0x28, 0x20, 0x48, 0x0b, 0x81, 0xad, 0x41, 0x63, 0x41, 0x0a, 0x0f, 0xc4, 0x4e, 0x5d,
0x9c, 0xd0, 0xf4, 0xf5, 0x22, 0xc0, 0x68, 0x55, 0xb0, 0x07, 0x6a, 0x1d, 0x95, 0x0a, 0x0c, 0x43, 0x05, 0x16, 0xdc,
0x28, 0x63, 0x84, 0x2a, 0x72, 0x93, 0xa4, 0xb1, 0x94, 0x90, 0x31, 0x1d, 0xbc, 0xda, 0xbb, 0xbb, 0xca, 0xf7, 0x3e,
0xeb, 0x8c, 0xf0, 0x8f, 0xe4, 0xaa, 0x9f, 0x4d, 0x26, 0x93, 0x1b, 0x85, 0xce, 0x67, 0xe3, 0x09, 0xeb, 0xb2, 0x07,
0x3d, 0x74, 0xfe, 0xb5, 0xa4, 0x2f, 0xae, 0x53, 0x12, 0xee, 0x96, 0x77, 0x6b, 0x8c, 0xce, 0x38, 0x90, 0x43, 0x77,
0x97, 0x4e, 0x25, 0x60, 0x65, 0x09, 0x5c, 0xf9, 0x34, 0x4e, 0x83, 0x76, 0xe9, 0x9f, 0x49, 0x76, 0xfe, 0xd9, 0xe3,
0xc7, 0x8f, 0x4b, 0x7f, 0xac, 0xde, 0xda, 0xe3, 0x71, 0xe9, 0x8f, 0x96, 0x7a, 0x19, 0xed, 0xf6, 0x64, 0x52, 0xfa,
0xb1, 0x2a, 0xd8, 0xed, 0x8e, 0xc6, 0xbb, 0xdd, 0xd2, 0x3f, 0x37, 0x5a, 0x94, 0x3e, 0x93, 0x6f, 0x39, 0x1b, 0xd7,
0x3c, 0x88, 0x8f, 0xc0, 0x78, 0xf5, 0x05, 0xa1, 0x2d, 0xd1, 0x64, 0x10, 0x8f, 0x41, 0xb4, 0xe0, 0x60, 0xeb, 0x02,
0x6f, 0x67, 0xc0, 0x9f, 0x27, 0x92, 0xb7, 0x8b, 0x4f, 0x7e, 0x22, 0x47, 0xff, 0xd5, 0xe4, 0xe8, 0x48, 0xcc, 0xc4,
0xcd, 0x19, 0xc9, 0x81, 0x66, 0x35, 0x52, 0x16, 0x55, 0xff, 0x1a, 0xb2, 0x8a, 0xd9, 0x23, 0xb7, 0xc1, 0x96, 0x82,
0xc7, 0x7f, 0x7d, 0x1d, 0x8f, 0xff, 0xe6, 0x76, 0x1e, 0x7f, 0x72, 0x37, 0x16, 0xff, 0xcd, 0x9f, 0xcc, 0xe2, 0xbf,
0x6e, 0xb2, 0xf8, 0xcd, 0x3b, 0xb1, 0xf8, 0x35, 0x89, 0x1f, 0xa4, 0x9a, 0xbe, 0x49, 0x43, 0xfb, 0x0d, 0xd8, 0x30,
0x46, 0xc9, 0x64, 0x02, 0x45, 0x93, 0x89, 0xad, 0x92, 0x1d, 0x81, 0x13, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5,
0xd5, 0x57, 0x66, 0x99, 0x59, 0x20, 0xfd, 0x0d, 0xa6, 0x7c, 0x57, 0x35, 0x52, 0x65, 0x56, 0x9f, 0x06, 0x19, 0xc7,
0x05, 0x9e, 0x26, 0x2c, 0x28, 0x79, 0x76, 0x7a, 0x9a, 0x30, 0xfd, 0xed, 0x33, 0xd5, 0xd2, 0x7c, 0x33, 0xe7, 0x33,
0xcb, 0x07, 0x26, 0xb4, 0x41, 0x0d, 0xd0, 0x9e, 0x70, 0x64, 0xd2, 0xe7, 0xa0, 0x45, 0xd8, 0xfa, 0x4c, 0x7e, 0x37,
0x98, 0xfc, 0xa9, 0x4b, 0xc9, 0x7e, 0x65, 0x40, 0xb3, 0xea, 0x8a, 0x2e, 0x4c, 0x91, 0x02, 0x32, 0x2e, 0x95, 0xdb,
0x12, 0xa0, 0x9d, 0xe3, 0x47, 0x4e, 0x74, 0xca, 0xd2, 0xca, 0x37, 0x85, 0x34, 0x9b, 0xc0, 0x8f, 0x1e, 0x88, 0x29,
0xc4, 0x67, 0x02, 0xf5, 0xb8, 0x22, 0x0e, 0xe8, 0xd4, 0xd6, 0x68, 0xac, 0x2a, 0x0c, 0xcd, 0xa5, 0xa8, 0x9c, 0x93,
0xd5, 0x79, 0xd6, 0x8a, 0xe6, 0xeb, 0x85, 0xf2, 0xdd, 0xa6, 0xbb, 0x45, 0x34, 0x14, 0xe7, 0x76, 0x5f, 0xdb, 0x98,
0x35, 0x9a, 0x29, 0xeb, 0x5e, 0x38, 0x9a, 0xe8, 0x24, 0xbb, 0xa8, 0xdb, 0x48, 0x26, 0x0c, 0x68, 0x3e, 0xe9, 0xbd,
0x57, 0x75, 0xaa, 0xa0, 0x34, 0xbd, 0xa2, 0x22, 0xd3, 0x8b, 0x48, 0x83, 0x7c, 0x60, 0xb0, 0x03, 0xa9, 0x60, 0xca,
0x30, 0x0f, 0x71, 0x17, 0x6d, 0x47, 0xa0, 0x32, 0x6d, 0x2b, 0x60, 0x51, 0x3a, 0xe4, 0xe8, 0x6b, 0xc2, 0x0e, 0x7d,
0xab, 0x06, 0x70, 0xaa, 0x6d, 0xb3, 0xdb, 0x19, 0x3e, 0x98, 0x16, 0xe7, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb,
0xf3, 0x25, 0xb0, 0xb0, 0x93, 0x4f, 0x31, 0x07, 0x85, 0x71, 0xde, 0x42, 0xa3, 0x98, 0xdc, 0x3b, 0x92, 0xd7, 0x53,
0xa8, 0x45, 0x5c, 0x89, 0xe8, 0x2d, 0x0a, 0xb4, 0x40, 0x48, 0xd5, 0x0e, 0xd2, 0x2c, 0x65, 0xbd, 0x7a, 0x48, 0xcd,
0xd4, 0x76, 0x15, 0xb6, 0x86, 0xcb, 0x0c, 0x2d, 0x16, 0x7e, 0x09, 0x16, 0x8b, 0x90, 0x11, 0x6d, 0x15, 0x8e, 0x69,
0xaf, 0x6d, 0x1f, 0x48, 0x64, 0x6e, 0x93, 0x28, 0xcc, 0x57, 0x55, 0xfa, 0xeb, 0x54, 0xf2, 0xdb, 0x02, 0x4c, 0xdd,
0x07, 0x0f, 0x3c, 0xf5, 0xcf, 0x88, 0xcc, 0x35, 0x8b, 0x29, 0xc0, 0x74, 0x17, 0xc8, 0x82, 0x68, 0x82, 0x5f, 0x10,
0xbb, 0x4b, 0xcb, 0x13, 0xca, 0xee, 0x5a, 0xa2, 0xcc, 0x0a, 0x3a, 0x8f, 0xc1, 0xc6, 0xb8, 0xf3, 0xf0, 0x37, 0x2f,
0xbf, 0x94, 0x38, 0x52, 0x97, 0xf4, 0x6c, 0xbb, 0x87, 0xa7, 0x39, 0x89, 0x2e, 0xc1, 0xd4, 0x21, 0x01, 0x7a, 0x82,
0xce, 0xae, 0xde, 0x3c, 0x93, 0x91, 0xd2, 0x9c, 0x25, 0xf4, 0x99, 0x7e, 0xb9, 0x15, 0xbb, 0x0f, 0xe7, 0x17, 0x6a,
0x37, 0x3a, 0x8d, 0x08, 0xe8, 0x9f, 0x1a, 0xe8, 0xbc, 0x3e, 0xb2, 0x5a, 0x0f, 0xd6, 0x3d, 0x00, 0x18, 0x84, 0xd4,
0x6e, 0xe5, 0x02, 0xaa, 0x36, 0x94, 0x18, 0xa1, 0xde, 0x6a, 0x20, 0xcb, 0xdf, 0x05, 0x09, 0x11, 0x81, 0xbd, 0x8b,
0x9f, 0x72, 0x8b, 0xc1, 0xa0, 0x92, 0x9a, 0xc1, 0x2c, 0x1e, 0x8f, 0x13, 0xd6, 0x53, 0xc2, 0xdf, 0xea, 0x3c, 0xc4,
0x48, 0xa9, 0xb9, 0x65, 0xf5, 0x5d, 0x31, 0x90, 0xa7, 0xf1, 0x14, 0x9d, 0x80, 0x32, 0x82, 0xdf, 0x63, 0x5b, 0x8b,
0x4e, 0x19, 0x42, 0x6c, 0x57, 0xc8, 0xa3, 0xe7, 0xfa, 0x5a, 0x1e, 0x80, 0x26, 0x44, 0x1b, 0x0e, 0x46, 0x75, 0x36,
0x0f, 0x5a, 0xbb, 0xf5, 0x85, 0x60, 0x95, 0x5e, 0x82, 0xb7, 0x66, 0x59, 0x1e, 0xd0, 0x44, 0x4b, 0x7c, 0xf8, 0xc7,
0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0x7e, 0xe9, 0xa2, 0xb2, 0xbe, 0x98, 0xff, 0x3f, 0xa7, 0xe5, 0x8b, 0xf5,
0xa7, 0xe5, 0x0b, 0x75, 0x5a, 0x6e, 0xa6, 0xd8, 0xcf, 0x26, 0x1d, 0xfc, 0xd3, 0xab, 0x16, 0x04, 0xbb, 0x02, 0xe8,
0xb0, 0x50, 0xe9, 0x6b, 0x75, 0xe1, 0x3f, 0x1a, 0xba, 0xed, 0xe1, 0x1f, 0x1f, 0xd4, 0x9b, 0xb6, 0x85, 0x85, 0xf8,
0xaf, 0x5d, 0xab, 0xea, 0xdc, 0xc7, 0x3a, 0xec, 0xf5, 0x60, 0xb5, 0xae, 0x7b, 0xf3, 0xa1, 0x05, 0x7e, 0xc5, 0x9d,
0x40, 0x31, 0x63, 0xb0, 0x43, 0xa2, 0x93, 0x13, 0x28, 0x9d, 0x64, 0xa3, 0x45, 0xf1, 0x8f, 0x12, 0x7e, 0x89, 0xc4,
0x1b, 0x8f, 0x74, 0x63, 0x1c, 0xd5, 0x55, 0x84, 0xdd, 0xd5, 0x08, 0x4b, 0xbd, 0x4f, 0x41, 0x01, 0x84, 0xc9, 0x9c,
0xae, 0x7f, 0x7f, 0xcd, 0x21, 0xf8, 0xbb, 0xec, 0xcd, 0xda, 0xc5, 0xfc, 0x7b, 0x91, 0x71, 0x23, 0x12, 0x7e, 0x17,
0x0e, 0xcc, 0x3d, 0x6c, 0x3f, 0x5e, 0x0f, 0xee, 0x91, 0x9a, 0x69, 0xa8, 0x84, 0x82, 0x94, 0x3b, 0xa0, 0xe2, 0x46,
0x8b, 0x84, 0xdf, 0x3c, 0xea, 0x75, 0x94, 0xb1, 0x32, 0xea, 0x0d, 0x0c, 0xbd, 0x6a, 0x7b, 0x47, 0x2e, 0xfd, 0xd9,
0x17, 0xf7, 0xf1, 0x8f, 0xf0, 0xea, 0x9c, 0x54, 0x8a, 0xbf, 0x30, 0x7c, 0x51, 0xf1, 0xdf, 0xac, 0x69, 0xf6, 0x42,
0x82, 0x93, 0x72, 0x7f, 0xd7, 0xd6, 0xa8, 0xcf, 0xde, 0xa9, 0xb9, 0xd4, 0x83, 0x7e, 0x57, 0xeb, 0xdf, 0x37, 0xf8,
0x1d, 0xdb, 0x8e, 0x84, 0xce, 0x5c, 0x6f, 0x2b, 0x7f, 0x65, 0xc2, 0x6a, 0x63, 0x81, 0xe7, 0xbb, 0x36, 0x57, 0x1b,
0x44, 0xed, 0x37, 0xc3, 0x13, 0x6d, 0x1e, 0xc9, 0xb0, 0x1b, 0xb6, 0x17, 0x16, 0xd2, 0xb7, 0x2c, 0xbc, 0x87, 0x9f,
0x1a, 0xb2, 0x2e, 0x66, 0x49, 0x0a, 0x3a, 0xd5, 0x94, 0xf3, 0x79, 0xb0, 0xb3, 0x73, 0x7e, 0x7e, 0xee, 0x9f, 0xef,
0xfa, 0x59, 0x7e, 0xba, 0xd3, 0x6d, 0xb7, 0xdb, 0xf8, 0x85, 0x18, 0xdb, 0x3a, 0x8b, 0xd9, 0xf9, 0x97, 0xd9, 0x45,
0x68, 0x3f, 0xb2, 0x1e, 0x5b, 0x8f, 0x76, 0xad, 0x07, 0x0f, 0x6d, 0x8b, 0xb8, 0x3f, 0x94, 0xec, 0xda, 0x96, 0xe0,
0xfe, 0xa1, 0x0d, 0xc5, 0xfd, 0xbd, 0x53, 0xa5, 0xc0, 0x61, 0x06, 0xae, 0x50, 0x8f, 0xc0, 0x66, 0xc9, 0x3e, 0xb1,
0xfa, 0x39, 0x17, 0x65, 0x2d, 0x29, 0x43, 0xd4, 0x2b, 0x1e, 0xf6, 0x51, 0x34, 0x0f, 0x88, 0x86, 0xcc, 0x42, 0x74,
0x00, 0x89, 0x52, 0x9a, 0x02, 0xa3, 0xba, 0x27, 0xf0, 0x04, 0x1a, 0xfb, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x47, 0x20,
0xe0, 0xce, 0x5a, 0xf7, 0x47, 0xed, 0x56, 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x23, 0xab, 0x2b, 0xfe, 0x07, 0x06, 0xb9,
0x6b, 0x75, 0xe0, 0x69, 0xd7, 0x82, 0xf7, 0xb3, 0xfb, 0x22, 0x24, 0x1c, 0xd9, 0x3b, 0xfd, 0x3d, 0xfc, 0x85, 0x29,
0xb0, 0xa8, 0x2f, 0x6c, 0xf1, 0x2b, 0x9e, 0xec, 0xcf, 0xcc, 0xd2, 0xce, 0xe3, 0xb5, 0xc5, 0xdd, 0x47, 0x6b, 0x8b,
0x77, 0x1f, 0xae, 0x2d, 0xbe, 0xff, 0xa0, 0x5e, 0xbc, 0x73, 0x2a, 0xaa, 0x34, 0x53, 0x08, 0xed, 0x59, 0x04, 0x54,
0x72, 0xe1, 0x74, 0x00, 0xce, 0xb6, 0xd5, 0xc2, 0x1f, 0x8f, 0xba, 0xae, 0xee, 0x75, 0x82, 0xbd, 0xf4, 0x2a, 0x1f,
0x3d, 0x86, 0x55, 0x3e, 0xef, 0x3e, 0x1c, 0x61, 0x3b, 0x5a, 0x28, 0xfc, 0x3b, 0xdb, 0x7d, 0x3c, 0x02, 0x71, 0x60,
0xe1, 0x3f, 0xf8, 0x33, 0x7d, 0xd0, 0x1d, 0x89, 0x97, 0x36, 0xd6, 0x7f, 0xe8, 0x3c, 0x2a, 0xa0, 0x29, 0xfe, 0xf9,
0x4d, 0xeb, 0xcf, 0xa8, 0xbe, 0x9b, 0xe3, 0xde, 0x07, 0x1c, 0x3d, 0x9e, 0x76, 0xfd, 0x2f, 0xce, 0x1e, 0xf9, 0x8f,
0xa7, 0x9d, 0x47, 0x1f, 0xc4, 0x5b, 0x02, 0x18, 0xfc, 0x02, 0xff, 0x7d, 0xd8, 0x6d, 0x83, 0x69, 0xeb, 0x3f, 0x3e,
0xdb, 0xf5, 0x77, 0x93, 0xd6, 0x43, 0xff, 0x31, 0xfe, 0xab, 0x86, 0x9b, 0x66, 0x33, 0x66, 0x5b, 0xb8, 0xdf, 0x0d,
0xbb, 0xd0, 0x9c, 0xa3, 0x7b, 0xdf, 0x7a, 0x70, 0xff, 0xf9, 0x63, 0xd8, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf7,
0xf8, 0x01, 0x11, 0x2f, 0x07, 0x8e, 0x18, 0xe6, 0xce, 0x29, 0xc4, 0xd1, 0xd7, 0x8a, 0xee, 0x79, 0x3f, 0x5e, 0x67,
0xda, 0xff, 0x70, 0xbb, 0x69, 0xff, 0xd7, 0x3b, 0xba, 0x6f, 0x7f, 0xf8, 0x93, 0x6d, 0xfb, 0x1f, 0x9b, 0xb6, 0xfd,
0x39, 0x5b, 0x31, 0xee, 0x9b, 0xf6, 0xd9, 0x21, 0x73, 0x8e, 0xbe, 0x65, 0x43, 0xcc, 0x13, 0x85, 0xd6, 0x7f, 0xad,
0x79, 0x3a, 0x32, 0x3c, 0xc8, 0xe7, 0x4c, 0x9c, 0xe4, 0xef, 0xaf, 0x43, 0x08, 0xe3, 0xb7, 0x22, 0xe4, 0xc5, 0xdd,
0xf0, 0x41, 0x9f, 0x16, 0xff, 0x13, 0xf1, 0xf1, 0xbd, 0x89, 0x8f, 0x9a, 0x2f, 0x99, 0x8c, 0x79, 0xb2, 0xc1, 0x0f,
0xe8, 0xdd, 0xb1, 0x77, 0x18, 0xbe, 0x15, 0xb6, 0x48, 0x7e, 0x7a, 0xf7, 0x7b, 0xfc, 0xde, 0x22, 0x8d, 0x32, 0xb4,
0xa5, 0x83, 0x62, 0x8e, 0x1f, 0xe3, 0x54, 0x2f, 0x67, 0x22, 0x55, 0x3f, 0xa4, 0x7b, 0x36, 0xf7, 0xb5, 0x73, 0x03,
0x33, 0x5b, 0xc3, 0x65, 0xc6, 0x23, 0xfc, 0x6d, 0x2f, 0x3c, 0xe6, 0x09, 0xde, 0x02, 0x94, 0x37, 0x66, 0x30, 0x11,
0xf3, 0x5b, 0x4c, 0x22, 0x55, 0xee, 0xef, 0x19, 0x3a, 0x0c, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x13,
0x0b, 0x63, 0xb6, 0x6a, 0x19, 0x9c, 0x94, 0xbc, 0xe9, 0xda, 0xea, 0x17, 0x8c, 0xe4, 0xf8, 0xc1, 0xa6, 0xf0, 0x48,
0xba, 0xce, 0x6c, 0xa9, 0xfe, 0xc3, 0xf8, 0xaa, 0x24, 0x47, 0xd6, 0x5d, 0xa9, 0x0c, 0xb6, 0xd0, 0x19, 0x3a, 0x7e,
0x17, 0x6c, 0x08, 0x2a, 0xc6, 0x0f, 0xe0, 0xfc, 0xe0, 0xb4, 0x76, 0x41, 0xa7, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b,
0x1f, 0xdf, 0x14, 0x7e, 0x83, 0x46, 0xa9, 0xa7, 0x7f, 0xe3, 0x12, 0x50, 0x86, 0xca, 0xf5, 0xff, 0xf2, 0xf2, 0x50,
0x5e, 0x72, 0xb5, 0xd1, 0x27, 0x49, 0xbe, 0xe8, 0xea, 0x03, 0x3b, 0xdb, 0x20, 0x2e, 0xe8, 0xd7, 0xde, 0x51, 0x50,
0x16, 0x25, 0x02, 0xe6, 0x98, 0x5a, 0xd2, 0x6c, 0x08, 0x6d, 0x21, 0x0f, 0xc6, 0xec, 0x2c, 0x1e, 0x49, 0xb6, 0xee,
0x59, 0x32, 0x37, 0xbe, 0x45, 0xab, 0x08, 0x3b, 0x9e, 0x30, 0x9c, 0xe1, 0x05, 0x65, 0x54, 0x98, 0x66, 0x76, 0xff,
0x5e, 0x4f, 0x43, 0x52, 0x4f, 0xcf, 0xb5, 0xf1, 0x77, 0xf0, 0x1d, 0x81, 0xa1, 0xf6, 0x8f, 0xe1, 0x3d, 0xfc, 0x2d,
0x7c, 0xf7, 0x86, 0xb6, 0xeb, 0x13, 0x53, 0xbc, 0x57, 0xfd, 0x2a, 0x3e, 0xe4, 0x08, 0xdb, 0x20, 0xbf, 0xbc, 0xbb,
0x0a, 0x32, 0x29, 0xb4, 0xba, 0x0f, 0x2a, 0xa1, 0x05, 0xcf, 0x06, 0x97, 0x02, 0x06, 0xda, 0xf5, 0x1f, 0x18, 0xac,
0xf0, 0xac, 0x85, 0x3f, 0x6b, 0xcc, 0xf0, 0x3e, 0x34, 0x50, 0xdc, 0xf0, 0x25, 0x34, 0xdf, 0x15, 0x8c, 0x17, 0xfa,
0xfd, 0x48, 0xac, 0x4a, 0xb0, 0xa9, 0x3a, 0xc5, 0xac, 0x09, 0x8f, 0x88, 0x78, 0xb6, 0xed, 0x39, 0xfa, 0xfb, 0xfe,
0x92, 0x5c, 0xe5, 0xe5, 0xa4, 0xa7, 0xd0, 0xd7, 0xd1, 0xdf, 0xad, 0x5d, 0x57, 0xe7, 0xd5, 0x4e, 0xce, 0x9a, 0x29,
0x90, 0xe0, 0x1b, 0x21, 0x18, 0xca, 0xd5, 0x16, 0xdf, 0x6f, 0x12, 0xc7, 0xb8, 0xfa, 0xc2, 0xd5, 0x9a, 0x74, 0x43,
0xf3, 0x50, 0xb0, 0x8a, 0x68, 0xe8, 0x5c, 0x00, 0x23, 0xa0, 0x9f, 0x55, 0xb1, 0x7a, 0x90, 0x04, 0xe5, 0x27, 0x11,
0xfe, 0xfa, 0x09, 0xfa, 0x51, 0x56, 0x07, 0x90, 0xd3, 0x07, 0xfa, 0x08, 0xd2, 0x17, 0xe3, 0xb2, 0xb9, 0x08, 0xd0,
0x17, 0xf0, 0xb7, 0x99, 0x55, 0xb9, 0xe1, 0xf2, 0xd2, 0x17, 0x86, 0xc1, 0xc7, 0x71, 0x4e, 0x77, 0x09, 0xd5, 0xfa,
0x6b, 0xd7, 0xfc, 0x2a, 0x54, 0xd3, 0xa9, 0x64, 0xc5, 0xc0, 0xc6, 0x22, 0x5b, 0x65, 0xe9, 0x98, 0x5f, 0xa8, 0x35,
0x2f, 0x7b, 0x8d, 0x45, 0x9a, 0x0e, 0x7e, 0xc1, 0xdb, 0x16, 0x48, 0xb6, 0x81, 0x8d, 0x5d, 0xbb, 0x26, 0x52, 0x6e,
0xf0, 0x8e, 0x54, 0xf5, 0x2b, 0x59, 0xcc, 0x03, 0x6f, 0x9b, 0xbb, 0xa5, 0xc7, 0xa5, 0x7d, 0x70, 0xa5, 0xa7, 0xf0,
0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0xf7, 0x70, 0x0c, 0xb6, 0xe0, 0x75, 0xd8, 0xae, 0x5b, 0x02, 0xe7, 0x31, 0x7e,
0x67, 0x8d, 0x40, 0xbd, 0x0f, 0x85, 0x6e, 0xe5, 0xb5, 0x9b, 0x76, 0xfb, 0x6f, 0x0e, 0xf7, 0x2d, 0x71, 0x9a, 0xf7,
0x76, 0xe0, 0x75, 0x8f, 0x6c, 0x61, 0x91, 0x52, 0x10, 0x8a, 0x94, 0x02, 0x4b, 0x64, 0xc3, 0x84, 0xf6, 0x8e, 0x58,
0xa6, 0x6d, 0xb1, 0x74, 0x24, 0x3c, 0x78, 0x33, 0xb0, 0x15, 0x62, 0xfc, 0x8a, 0xd1, 0x0e, 0x76, 0x6b, 0xe1, 0x4e,
0xc3, 0x11, 0x10, 0x3e, 0x3e, 0xa5, 0x20, 0xf0, 0xd4, 0x96, 0xfe, 0x3e, 0x10, 0xeb, 0x4c, 0x65, 0x62, 0xc8, 0xa1,
0x74, 0x5e, 0xde, 0x6a, 0xeb, 0x62, 0x71, 0x32, 0x03, 0x3e, 0xa4, 0x92, 0x29, 0xde, 0xcb, 0x0e, 0x7b, 0x34, 0x15,
0x66, 0x01, 0xae, 0x3a, 0x21, 0xa7, 0x9d, 0xfe, 0x5e, 0x24, 0xf5, 0x1d, 0x3c, 0xbb, 0x05, 0x1c, 0x5e, 0x10, 0x73,
0xa8, 0x54, 0xf8, 0x71, 0xb6, 0x73, 0xce, 0x4e, 0x5a, 0xd1, 0x3c, 0xae, 0x7c, 0x7f, 0x28, 0xfd, 0xfa, 0x7b, 0x4a,
0x10, 0xca, 0x84, 0x33, 0xf9, 0x18, 0x19, 0x89, 0x07, 0x88, 0x38, 0x22, 0xd0, 0x52, 0x3a, 0x16, 0x49, 0x69, 0x04,
0xe4, 0x03, 0xac, 0x44, 0xbf, 0xca, 0x01, 0x29, 0x25, 0x41, 0x69, 0xf7, 0xff, 0xf6, 0xbf, 0xfe, 0xb7, 0xf4, 0x29,
0x02, 0x5a, 0x01, 0x2c, 0xcc, 0xdc, 0xa8, 0x62, 0x67, 0xec, 0x02, 0xac, 0xd0, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04,
0x20, 0x28, 0x98, 0xb8, 0xbb, 0x21, 0xeb, 0x81, 0x0a, 0x24, 0x58, 0x66, 0xd8, 0x59, 0x82, 0x57, 0x2f, 0xc2, 0x1d,
0xfb, 0x43, 0x19, 0x7c, 0x2a, 0xb7, 0x94, 0x08, 0xda, 0xc8, 0xe7, 0x33, 0x68, 0xae, 0x96, 0xd3, 0xa7, 0x7e, 0x23,
0x8c, 0x64, 0x1e, 0xac, 0x96, 0xd0, 0x07, 0x2d, 0x75, 0xa0, 0xe0, 0xdf, 0xfe, 0xf5, 0x3f, 0xff, 0x77, 0xf5, 0x8a,
0xfe, 0xff, 0xbf, 0xfd, 0xcb, 0x3f, 0xfd, 0xdf, 0xff, 0xf3, 0x5f, 0x30, 0x39, 0x52, 0xc6, 0x08, 0xe8, 0x28, 0x59,
0x55, 0x80, 0x40, 0x9c, 0xa9, 0x7a, 0xb6, 0xdf, 0x01, 0xcd, 0x42, 0x04, 0x29, 0x41, 0x22, 0x62, 0xa6, 0x24, 0x50,
0x42, 0xd5, 0x0d, 0x38, 0x83, 0xfd, 0xb3, 0x28, 0x4a, 0x6d, 0x3f, 0x68, 0xdb, 0xd5, 0x9e, 0xf6, 0x8d, 0xbe, 0x3b,
0xb8, 0x1b, 0x77, 0xca, 0x14, 0xf1, 0xf5, 0x5e, 0x2d, 0x95, 0xe3, 0x0a, 0x4b, 0xca, 0xaa, 0xdc, 0x42, 0x8f, 0xf2,
0x12, 0x5f, 0x83, 0xae, 0x51, 0x4c, 0x5b, 0x5b, 0xeb, 0xd3, 0xfb, 0x65, 0x51, 0xf0, 0x78, 0x82, 0xfb, 0x21, 0xdc,
0x63, 0x14, 0x0a, 0x6c, 0xa1, 0x4a, 0x92, 0x5c, 0x96, 0x34, 0x8a, 0x30, 0x61, 0xee, 0x3f, 0xfe, 0x87, 0xf2, 0x2f,
0x33, 0x54, 0x05, 0x2c, 0x67, 0x16, 0x5d, 0x48, 0xc3, 0xe6, 0x61, 0xbb, 0x3d, 0xbf, 0x70, 0x97, 0xd5, 0x0c, 0xde,
0x75, 0x93, 0x91, 0x4b, 0xcd, 0x1c, 0x90, 0x62, 0x88, 0xda, 0x7b, 0x07, 0xba, 0x7c, 0x1b, 0x9d, 0x3d, 0x65, 0xf9,
0xf9, 0x92, 0x1c, 0x48, 0xf1, 0x6f, 0x18, 0xeb, 0x93, 0xbe, 0x36, 0x28, 0x31, 0x56, 0xb1, 0x34, 0x7a, 0x75, 0x45,
0xaf, 0x69, 0x67, 0x35, 0xd3, 0xc4, 0x8c, 0x55, 0x9a, 0x51, 0x46, 0xcc, 0xc3, 0x80, 0x0e, 0xde, 0xb4, 0xbb, 0xd4,
0xc3, 0x73, 0x9e, 0xcd, 0xcc, 0xe0, 0x24, 0x8b, 0xd8, 0x88, 0x4d, 0x94, 0x8f, 0x52, 0xd6, 0x8b, 0xc0, 0x63, 0xf9,
0x19, 0x9e, 0x31, 0xc0, 0x6d, 0x16, 0xf1, 0x80, 0x28, 0xb5, 0x67, 0x86, 0x2f, 0x23, 0x0c, 0x0c, 0x67, 0x4b, 0x63,
0xae, 0x9e, 0x68, 0x8a, 0x9e, 0xc0, 0x7a, 0x7e, 0x4a, 0xe9, 0x53, 0x77, 0x73, 0x28, 0xe1, 0x48, 0x78, 0x51, 0x65,
0x87, 0x54, 0x26, 0xf6, 0xbb, 0x9a, 0x39, 0x2e, 0x99, 0x31, 0x18, 0xc1, 0xb7, 0x37, 0x16, 0x52, 0x52, 0x34, 0xfd,
0x15, 0x94, 0x1f, 0x5a, 0x80, 0xdd, 0x6c, 0x45, 0x85, 0xd8, 0xea, 0x5d, 0xf8, 0x42, 0xab, 0xe2, 0xd1, 0x7c, 0x4e,
0x0d, 0x5d, 0xa0, 0x53, 0x52, 0xa9, 0x91, 0x71, 0x50, 0x2c, 0x5c, 0x84, 0x9e, 0x65, 0x1b, 0x49, 0xd0, 0xe2, 0x49,
0x06, 0xa5, 0xe9, 0xf7, 0x0d, 0xff, 0x3f, 0xdf, 0x8d, 0x21, 0x2b, 0x85, 0x78, 0x00, 0x00};
} // namespace web_server
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#ifdef USE_ARDUINO
#include "web_server.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
@@ -17,18 +18,27 @@
#endif
#ifdef USE_LOGGER
#include <esphome/components/logger/logger.h>
#include "esphome/components/logger/logger.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_helpers.h"
#endif
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate.h"
#endif
#ifdef USE_WEBSERVER_LOCAL
#include "server_index.h"
#endif
namespace esphome {
namespace web_server {
static const char *const TAG = "web_server";
#if USE_WEBSERVER_VERSION == 1
void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
const std::function<void(AsyncResponseStream &stream, EntityBase *obj)> &action_func = nullptr) {
stream->print("<tr class=\"");
@@ -49,6 +59,7 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &
stream->print("</td>");
stream->print("</tr>");
}
#endif
UrlMatch match_url(const std::string &url, bool only_domain = false) {
UrlMatch match;
@@ -87,78 +98,16 @@ void WebServer::setup() {
this->base_->init();
this->events_.onConnect([this](AsyncEventSourceClient *client) {
// Configure reconnect timeout
client->send("", "ping", millis(), 30000);
// Configure reconnect timeout and send config
#ifdef USE_SENSOR
for (auto *obj : App.get_sensors()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->sensor_json(obj, obj->state).c_str(), "state");
}
#endif
client->send(json::build_json([this](JsonObject root) {
root["title"] = App.get_name();
root["ota"] = this->allow_ota_;
root["lang"] = "en";
}).c_str(),
"ping", millis(), 30000);
#ifdef USE_SWITCH
for (auto *obj : App.get_switches()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->switch_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_BINARY_SENSOR
for (auto *obj : App.get_binary_sensors()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_FAN
for (auto *obj : App.get_fans()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->fan_json(obj).c_str(), "state");
}
#endif
#ifdef USE_LIGHT
for (auto *obj : App.get_lights()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->light_json(obj).c_str(), "state");
}
#endif
#ifdef USE_TEXT_SENSOR
for (auto *obj : App.get_text_sensors()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->text_sensor_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_COVER
for (auto *obj : App.get_covers()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->cover_json(obj).c_str(), "state");
}
#endif
#ifdef USE_NUMBER
for (auto *obj : App.get_numbers()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->number_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_SELECT
for (auto *obj : App.get_selects()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->select_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_LOCK
for (auto *obj : App.get_locks()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->lock_json(obj, obj->state).c_str(), "state");
}
#endif
this->entities_iterator_.begin(this->include_internal_);
});
#ifdef USE_LOGGER
@@ -175,21 +124,34 @@ void WebServer::setup() {
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
}
void WebServer::loop() { this->entities_iterator_.advance(); }
void WebServer::dump_config() {
ESP_LOGCONFIG(TAG, "Web Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port());
}
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
#ifdef USE_WEBSERVER_LOCAL
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
}
#else
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("text/html");
std::string title = App.get_name() + " Web Server";
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8>"
"<meta name=\"viewport\" content=\"width=device-width, "
"initial-scale=1.0\"><title>"));
// All content is controlled and created by user - so allowing all origins is fine here.
stream->addHeader("Access-Control-Allow-Origin", "*");
#if USE_WEBSERVER_VERSION == 1
const std::string &title = App.get_name();
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
"name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
stream->print(title.c_str());
stream->print(F("</title>"));
#ifdef WEBSERVER_CSS_INCLUDE
#else
stream->print(F("<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>"));
#endif
#ifdef USE_WEBSERVER_CSS_INCLUDE
stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
#endif
if (strlen(this->css_url_) > 0) {
@@ -197,11 +159,12 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
stream->print(this->css_url_);
stream->print(F("\">"));
}
stream->print(F("</head><body><article class=\"markdown-body\"><h1>"));
stream->print(F("</head><body>"));
#if USE_WEBSERVER_VERSION == 1
stream->print(F("<article class=\"markdown-body\"><h1>"));
stream->print(title.c_str());
stream->print(F("</h1><h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
// All content is controlled and created by user - so allowing all origins is fine here.
stream->addHeader("Access-Control-Allow-Origin", "*");
stream->print(F("</h1>"));
stream->print(F("<h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
#ifdef USE_SENSOR
for (auto *obj : App.get_sensors()) {
@@ -308,6 +271,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
}
#endif
#ifdef USE_CLIMATE
for (auto *obj : App.get_climates()) {
if (this->include_internal_ || !obj->is_internal())
write_row(stream, obj, "climate", "");
}
#endif
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
"REST API documentation.</p>"));
if (this->allow_ota_) {
@@ -316,23 +286,30 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
}
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
#ifdef WEBSERVER_JS_INCLUDE
#endif
#ifdef USE_WEBSERVER_JS_INCLUDE
if (this->js_include_ != nullptr) {
stream->print(F("<script src=\"/0.js\"></script>"));
stream->print(F("<script type=\"module\" src=\"/0.js\"></script>"));
}
#endif
#if USE_WEBSERVER_VERSION == 2
stream->print(F("<esp-app></esp-app>"));
#endif
if (strlen(this->js_url_) > 0) {
stream->print(F("<script src=\""));
stream->print(this->js_url_);
stream->print(F("\"></script>"));
}
#if USE_WEBSERVER_VERSION == 1
stream->print(F("</article></body></html>"));
#else
stream->print(F("</body></html>"));
#endif
request->send(stream);
}
#ifdef WEBSERVER_CSS_INCLUDE
#endif
#ifdef USE_WEBSERVER_CSS_INCLUDE
void WebServer::handle_css_request(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("text/css");
if (this->css_include_ != nullptr) {
@@ -343,10 +320,11 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) {
}
#endif
#ifdef WEBSERVER_JS_INCLUDE
#ifdef USE_WEBSERVER_JS_INCLUDE
void WebServer::handle_js_request(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("text/javascript");
if (this->js_include_ != nullptr) {
stream->addHeader("Access-Control-Allow-Origin", "*");
stream->print(this->js_include_);
}
@@ -354,64 +332,75 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
}
#endif
#define set_json_id(root, obj, sensor, start_config) \
(root)["id"] = sensor; \
if (((start_config) == DETAIL_ALL)) \
(root)["name"] = (obj)->get_name();
#define set_json_value(root, obj, sensor, value, start_config) \
set_json_id((root), (obj), sensor, start_config)(root)["value"] = value;
#define set_json_state_value(root, obj, sensor, state, value, start_config) \
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state;
#define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \
if (((start_config) == DETAIL_ALL)) \
(root)["icon"] = (obj)->get_icon();
#ifdef USE_SENSOR
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
this->events_.send(this->sensor_json(obj, state).c_str(), "state");
this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (sensor::Sensor *obj : App.get_sensors()) {
if (obj->get_object_id() != match.id)
continue;
std::string data = this->sensor_json(obj, obj->state);
std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
request->send(404);
}
std::string WebServer::sensor_json(sensor::Sensor *obj, float value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "sensor-" + obj->get_object_id();
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
if (!obj->get_unit_of_measurement().empty())
state += " " + obj->get_unit_of_measurement();
root["state"] = state;
root["value"] = value;
set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
});
}
#endif
#ifdef USE_TEXT_SENSOR
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
this->events_.send(this->text_sensor_json(obj, state).c_str(), "state");
this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
if (obj->get_object_id() != match.id)
continue;
std::string data = this->text_sensor_json(obj, obj->state);
std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
request->send(404);
}
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "text_sensor-" + obj->get_object_id();
root["state"] = value;
root["value"] = value;
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
});
}
#endif
#ifdef USE_SWITCH
void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
this->events_.send(this->switch_json(obj, state).c_str(), "state");
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::switch_json(switch_::Switch *obj, bool value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "switch-" + obj->get_object_id();
root["state"] = value ? "ON" : "OFF";
root["value"] = value;
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
});
}
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@@ -420,7 +409,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
continue;
if (request->method() == HTTP_GET) {
std::string data = this->switch_json(obj, obj->state);
std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
} else if (match.method == "toggle") {
this->defer([obj]() { obj->toggle(); });
@@ -441,14 +430,19 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
#endif
#ifdef USE_BUTTON
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
return json::build_json(
[obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); });
}
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (button::Button *obj : App.get_buttons()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_POST && match.method == "press") {
this->defer([obj]() { obj->press(); });
request->send(200);
return;
} else {
request->send(404);
}
@@ -460,20 +454,18 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
#ifdef USE_BINARY_SENSOR
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state");
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "binary_sensor-" + obj->get_object_id();
root["state"] = value ? "ON" : "OFF";
root["value"] = value;
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
});
}
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
if (obj->get_object_id() != match.id)
continue;
std::string data = this->binary_sensor_json(obj, obj->state);
std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
@@ -482,15 +474,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
#endif
#ifdef USE_FAN
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); }
std::string WebServer::fan_json(fan::Fan *obj) {
return json::build_json([obj](JsonObject root) {
root["id"] = "fan-" + obj->get_object_id();
root["state"] = obj->state ? "ON" : "OFF";
root["value"] = obj->state;
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); }
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config);
const auto traits = obj->get_traits();
if (traits.supports_speed()) {
root["speed_level"] = obj->speed;
root["speed_count"] = traits.supported_speed_count();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
@@ -517,7 +509,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
continue;
if (request->method() == HTTP_GET) {
std::string data = this->fan_json(obj);
std::string data = this->fan_json(obj, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
} else if (match.method == "toggle") {
this->defer([obj]() { obj->toggle().perform(); });
@@ -573,14 +565,16 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
#endif
#ifdef USE_LIGHT
void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); }
void WebServer::on_light_update(light::LightState *obj) {
this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (light::LightState *obj : App.get_lights()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->light_json(obj);
std::string data = this->light_json(obj, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
} else if (match.method == "toggle") {
this->defer([obj]() { obj->toggle().perform(); });
@@ -632,24 +626,34 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::light_json(light::LightState *obj) {
return json::build_json([obj](JsonObject root) {
root["id"] = "light-" + obj->get_object_id();
std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
light::LightJSONSchema::dump_json(*obj, root);
if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("effects");
opt.add("None");
for (auto const &option : obj->get_effects()) {
opt.add(option->get_name());
}
}
});
}
#endif
#ifdef USE_COVER
void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); }
void WebServer::on_cover_update(cover::Cover *obj) {
this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (cover::Cover *obj : App.get_covers()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->cover_json(obj);
std::string data = this->cover_json(obj, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
continue;
}
@@ -684,11 +688,10 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::cover_json(cover::Cover *obj) {
return json::build_json([obj](JsonObject root) {
root["id"] = "cover-" + obj->get_object_id();
root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN";
root["value"] = obj->position;
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
obj->position, start_config);
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_tilt())
@@ -699,7 +702,7 @@ std::string WebServer::cover_json(cover::Cover *obj) {
#ifdef USE_NUMBER
void WebServer::on_number_update(number::Number *obj, float state) {
this->events_.send(this->number_json(obj, state).c_str(), "state");
this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_numbers()) {
@@ -707,18 +710,16 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
continue;
if (request->method() == HTTP_GET) {
std::string data = this->number_json(obj, obj->state);
std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
if (match.method != "set") {
request->send(404);
return;
}
auto call = obj->make_call();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
optional<float> value_f = parse_number<float>(value.c_str());
@@ -732,19 +733,30 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::number_json(number::Number *obj, float value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "number-" + obj->get_object_id();
std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
if (start_config == DETAIL_ALL) {
root["min_value"] = obj->traits.get_min_value();
root["max_value"] = obj->traits.get_max_value();
root["step"] = obj->traits.get_step();
root["mode"] = (int) obj->traits.get_mode();
}
std::string state = str_sprintf("%f", value);
root["state"] = state;
root["value"] = value;
if (isnan(value)) {
root["value"] = "\"NaN\"";
} else {
root["value"] = value;
}
});
}
#endif
#ifdef USE_SELECT
void WebServer::on_select_update(select::Select *obj, const std::string &state) {
this->events_.send(this->select_json(obj, state).c_str(), "state");
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_selects()) {
@@ -752,7 +764,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
continue;
if (request->method() == HTTP_GET) {
std::string data = this->select_json(obj, obj->state);
std::string data = this->select_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
@@ -775,24 +787,160 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::select_json(select::Select *obj, const std::string &value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "select-" + obj->get_object_id();
root["state"] = value;
root["value"] = value;
std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("option");
for (auto &option : obj->traits.get_options()) {
opt.add(option);
}
}
});
}
#endif
#ifdef USE_CLIMATE
void WebServer::on_climate_update(climate::Climate *obj) {
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_climates()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->climate_json(obj, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
}
if (match.method != "set") {
request->send(404);
return;
}
auto call = obj->make_call();
if (request->hasParam("mode")) {
String mode = request->getParam("mode")->value();
call.set_mode(mode.c_str());
}
if (request->hasParam("target_temperature_high")) {
String value = request->getParam("target_temperature_high")->value();
optional<float> value_f = parse_number<float>(value.c_str());
if (value_f.has_value())
call.set_target_temperature_high(*value_f);
}
if (request->hasParam("target_temperature_low")) {
String value = request->getParam("target_temperature_low")->value();
optional<float> value_f = parse_number<float>(value.c_str());
if (value_f.has_value())
call.set_target_temperature_low(*value_f);
}
if (request->hasParam("target_temperature")) {
String value = request->getParam("target_temperature")->value();
optional<float> value_f = parse_number<float>(value.c_str());
if (value_f.has_value())
call.set_target_temperature(*value_f);
}
this->defer([call]() mutable { call.perform(); });
request->send(200);
return;
}
request->send(404);
}
// Longest: HORIZONTAL
#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15)
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits();
char __buf[16];
if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("modes");
for (climate::ClimateMode m : traits.get_supported_modes())
opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
if (!traits.get_supported_custom_fan_modes().empty()) {
JsonArray opt = root.createNestedArray("fan_modes");
for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
}
if (!traits.get_supported_custom_fan_modes().empty()) {
JsonArray opt = root.createNestedArray("custom_fan_modes");
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
opt.add(custom_fan_mode);
}
if (traits.get_supports_swing_modes()) {
JsonArray opt = root.createNestedArray("swing_modes");
for (auto swing_mode : traits.get_supported_swing_modes())
opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
}
if (traits.get_supports_presets() && obj->preset.has_value()) {
JsonArray opt = root.createNestedArray("presets");
for (climate::ClimatePreset m : traits.get_supported_presets())
opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
}
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
JsonArray opt = root.createNestedArray("custom_presets");
for (auto const &custom_preset : traits.get_supported_custom_presets())
opt.add(custom_preset);
}
}
root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
root["max_temp"] = traits.get_visual_max_temperature();
root["min_temp"] = traits.get_visual_min_temperature();
root["step"] = traits.get_visual_temperature_step();
if (traits.get_supports_action()) {
root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
}
if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
}
if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) {
root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str();
}
if (traits.get_supports_presets() && obj->preset.has_value()) {
root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
}
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
root["custom_preset"] = obj->custom_preset.value().c_str();
}
if (traits.get_supports_swing_modes()) {
root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
}
if (traits.get_supports_current_temperature()) {
root["current_temperature"] = obj->current_temperature;
}
if (traits.get_supports_two_point_target_temperature()) {
root["current_temperature_low"] = obj->target_temperature_low;
root["current_temperature_high"] = obj->target_temperature_low;
} else {
root["target_temperature"] = obj->target_temperature;
root["state"] = obj->target_temperature;
}
});
}
#endif
#ifdef USE_LOCK
void WebServer::on_lock_update(lock::Lock *obj) {
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "lock-" + obj->get_object_id();
root["state"] = lock::lock_state_to_string(value);
root["value"] = value;
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
start_config);
});
}
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@@ -801,7 +949,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
continue;
if (request->method() == HTTP_GET) {
std::string data = this->lock_json(obj, obj->state);
std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
} else if (match.method == "lock") {
this->defer([obj]() { obj->lock(); });
@@ -825,12 +973,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/")
return true;
#ifdef WEBSERVER_CSS_INCLUDE
#ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css")
return true;
#endif
#ifdef WEBSERVER_JS_INCLUDE
#ifdef USE_WEBSERVER_JS_INCLUDE
if (request->url() == "/0.js")
return true;
#endif
@@ -888,6 +1036,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true;
#endif
#ifdef USE_CLIMATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
return true;
#endif
#ifdef USE_LOCK
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
return true;
@@ -901,14 +1054,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return;
}
#ifdef WEBSERVER_CSS_INCLUDE
#ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css") {
this->handle_css_request(request);
return;
}
#endif
#ifdef WEBSERVER_JS_INCLUDE
#ifdef USE_WEBSERVER_JS_INCLUDE
if (request->url() == "/0.js") {
this->handle_js_request(request);
return;
@@ -986,9 +1139,17 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
}
#endif
#ifdef USE_CLIMATE
if (match.domain == "climate") {
this->handle_climate_request(request, match);
return;
}
#endif
#ifdef USE_LOCK
if (match.domain == "lock") {
this->handle_lock_request(request, match);
return;
}
#endif

View File

@@ -2,9 +2,11 @@
#ifdef USE_ARDUINO
#include "list_entities.h"
#include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/components/web_server_base/web_server_base.h"
#include <vector>
@@ -19,6 +21,8 @@ struct UrlMatch {
bool valid; ///< Whether this match is valid
};
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
/** This class allows users to create a web server with their ESP nodes.
*
* Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things:
@@ -30,7 +34,7 @@ struct UrlMatch {
*/
class WebServer : public Controller, public Component, public AsyncWebHandler {
public:
WebServer(web_server_base::WebServerBase *base) : base_(base) {}
WebServer(web_server_base::WebServerBase *base) : base_(base), entities_iterator_(ListEntitiesIterator(this)) {}
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
* https://esphome.io/_static/webserver-v1.min.css
@@ -74,6 +78,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
// (In most use cases you won't need these)
/// Setup the internal web server and register handlers.
void setup() override;
void loop() override;
void dump_config() override;
@@ -83,12 +88,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle an index request under '/'.
void handle_index_request(AsyncWebServerRequest *request);
#ifdef WEBSERVER_CSS_INCLUDE
#ifdef USE_WEBSERVER_CSS_INCLUDE
/// Handle included css request under '/0.css'.
void handle_css_request(AsyncWebServerRequest *request);
#endif
#ifdef WEBSERVER_JS_INCLUDE
#ifdef USE_WEBSERVER_JS_INCLUDE
/// Handle included js request under '/0.js'.
void handle_js_request(AsyncWebServerRequest *request);
#endif
@@ -99,7 +104,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the sensor state with its value as a JSON string.
std::string sensor_json(sensor::Sensor *obj, float value);
std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config);
#endif
#ifdef USE_SWITCH
@@ -109,12 +114,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the switch state with its value as a JSON string.
std::string switch_json(switch_::Switch *obj, bool value);
std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config);
#endif
#ifdef USE_BUTTON
/// Handle a button request under '/button/<id>/press'.
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the button details with its value as a JSON string.
std::string button_json(button::Button *obj, JsonDetail start_config);
#endif
#ifdef USE_BINARY_SENSOR
@@ -124,7 +132,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the binary sensor state with its value as a JSON string.
std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value);
std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config);
#endif
#ifdef USE_FAN
@@ -134,7 +142,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the fan state as a JSON string.
std::string fan_json(fan::Fan *obj);
std::string fan_json(fan::Fan *obj, JsonDetail start_config);
#endif
#ifdef USE_LIGHT
@@ -144,7 +152,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the light state as a JSON string.
std::string light_json(light::LightState *obj);
std::string light_json(light::LightState *obj, JsonDetail start_config);
#endif
#ifdef USE_TEXT_SENSOR
@@ -154,7 +162,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the text sensor state with its value as a JSON string.
std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value);
std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config);
#endif
#ifdef USE_COVER
@@ -164,7 +172,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the cover state as a JSON string.
std::string cover_json(cover::Cover *obj);
std::string cover_json(cover::Cover *obj, JsonDetail start_config);
#endif
#ifdef USE_NUMBER
@@ -173,7 +181,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the number state with its value as a JSON string.
std::string number_json(number::Number *obj, float value);
std::string number_json(number::Number *obj, float value, JsonDetail start_config);
#endif
#ifdef USE_SELECT
@@ -181,8 +189,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a select request under '/select/<id>'.
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the number state with its value as a JSON string.
std::string select_json(select::Select *obj, const std::string &value);
/// Dump the select state with its value as a JSON string.
std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config);
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
/// Handle a climate request under '/climate/<id>'.
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the climate details
std::string climate_json(climate::Climate *obj, JsonDetail start_config);
#endif
#ifdef USE_LOCK
@@ -192,7 +209,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the lock state with its value as a JSON string.
std::string lock_json(lock::Lock *obj, lock::LockState value);
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method.
@@ -203,8 +220,10 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
bool isRequestHandlerTrivial() override;
protected:
friend ListEntitiesIterator;
web_server_base::WebServerBase *base_;
AsyncEventSource events_{"/events"};
ListEntitiesIterator entities_iterator_;
const char *css_url_{nullptr};
const char *css_include_{nullptr};
const char *js_url_{nullptr};

View File

@@ -1,6 +1,6 @@
#include "xiaomi_ble.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -12,67 +12,74 @@ namespace xiaomi_ble {
static const char *const TAG = "xiaomi_ble";
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
// button pressed, 3 bytes, only byte 3 is used for supported devices so far
if ((value_type == 0x1001) && (value_length == 3)) {
result.button_press = data[2] == 0;
return true;
}
// motion detection, 1 byte, 8-bit unsigned integer
if ((value_type == 0x03) && (value_length == 1)) {
else if ((value_type == 0x0003) && (value_length == 1)) {
result.has_motion = data[0];
}
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
else if ((value_type == 0x04) && (value_length == 2)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
else if ((value_type == 0x1004) && (value_length == 2)) {
const int16_t temperature = encode_uint16(data[1], data[0]);
result.temperature = temperature / 10.0f;
}
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
else if ((value_type == 0x06) && (value_length == 2)) {
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
else if ((value_type == 0x1006) && (value_length == 2)) {
const int16_t humidity = encode_uint16(data[1], data[0]);
result.humidity = humidity / 10.0f;
}
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) {
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
else if (((value_type == 0x1007) || (value_type == 0x000F)) && (value_length == 3)) {
const uint32_t illuminance = encode_uint24(data[2], data[1], data[0]);
result.illuminance = illuminance;
result.is_light = illuminance == 100;
result.is_light = illuminance >= 100;
if (value_type == 0x0F)
result.has_motion = true;
}
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x08) && (value_length == 1)) {
else if ((value_type == 0x1008) && (value_length == 1)) {
result.moisture = data[0];
}
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
else if ((value_type == 0x09) && (value_length == 2)) {
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
else if ((value_type == 0x1009) && (value_length == 2)) {
const uint16_t conductivity = encode_uint16(data[1], data[0]);
result.conductivity = conductivity;
}
// battery, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x0A) && (value_length == 1)) {
else if ((value_type == 0x100A) && (value_length == 1)) {
result.battery_level = data[0];
}
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
else if ((value_type == 0x0D) && (value_length == 4)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
else if ((value_type == 0x100D) && (value_length == 4)) {
const int16_t temperature = encode_uint16(data[1], data[0]);
const int16_t humidity = encode_uint16(data[3], data[2]);
result.temperature = temperature / 10.0f;
result.humidity = humidity / 10.0f;
}
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
else if ((value_type == 0x10) && (value_length == 2)) {
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
else if ((value_type == 0x1010) && (value_length == 2)) {
const uint16_t formaldehyde = encode_uint16(data[1], data[0]);
result.formaldehyde = formaldehyde / 100.0f;
}
// on/off state, 1 byte, 8-bit unsigned integer
else if ((value_type == 0x12) && (value_length == 1)) {
else if ((value_type == 0x1012) && (value_length == 1)) {
result.is_active = data[0];
}
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x13) && (value_length == 1)) {
else if ((value_type == 0x1013) && (value_length == 1)) {
result.tablet = data[0];
}
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
else if ((value_type == 0x17) && (value_length == 4)) {
else if ((value_type == 0x1017) && (value_length == 4)) {
const uint32_t idle_time = encode_uint32(data[3], data[2], data[1], data[0]);
result.idle_time = idle_time / 60.0f;
result.has_motion = !idle_time;
} else if ((value_type == 0x1018) && (value_length == 1)) {
result.is_light = data[0];
} else {
return false;
}
@@ -115,7 +122,7 @@ bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult
break;
}
const uint8_t value_type = payload[payload_offset + 0];
const uint16_t value_type = encode_uint16(payload[payload_offset + 1], payload[payload_offset + 0]);
const uint8_t *data = &payload[payload_offset + 3];
if (parse_xiaomi_value(value_type, data, value_length, result))
@@ -155,60 +162,67 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
result.is_duplicate = false;
result.raw_offset = result.has_capability ? 12 : 11;
if ((raw[2] == 0x98) && (raw[3] == 0x00)) { // MiFlora
const uint16_t device_uuid = encode_uint16(raw[3], raw[2]);
if (device_uuid == 0x0098) { // MiFlora
result.type = XiaomiParseResult::TYPE_HHCCJCY01;
result.name = "HHCCJCY01";
} else if ((raw[2] == 0xaa) && (raw[3] == 0x01)) { // round body, segment LCD
} else if (device_uuid == 0x01aa) { // round body, segment LCD
result.type = XiaomiParseResult::TYPE_LYWSDCGQ;
result.name = "LYWSDCGQ";
} else if ((raw[2] == 0x5d) && (raw[3] == 0x01)) { // FlowerPot, RoPot
} else if (device_uuid == 0x015d) { // FlowerPot, RoPot
result.type = XiaomiParseResult::TYPE_HHCCPOT002;
result.name = "HHCCPOT002";
} else if ((raw[2] == 0xdf) && (raw[3] == 0x02)) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display
} else if (device_uuid == 0x02df) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display
result.type = XiaomiParseResult::TYPE_JQJCY01YM;
result.name = "JQJCY01YM";
} else if ((raw[2] == 0xdd) && (raw[3] == 0x03)) { // Philips/Xiaomi BLE nightlight
} else if (device_uuid == 0x03dd) { // Philips/Xiaomi BLE nightlight
result.type = XiaomiParseResult::TYPE_MUE4094RT;
result.name = "MUE4094RT";
result.raw_offset -= 6;
} else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display
(raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys
} else if (device_uuid == 0x0347 || // ClearGrass-branded, round body, e-ink display
device_uuid == 0x0B48) { // Qingping-branded, round body, e-ink display — with bindkeys
result.type = XiaomiParseResult::TYPE_CGG1;
result.name = "CGG1";
} else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden
} else if (device_uuid == 0x03bc) { // VegTrug Grow Care Garden
result.type = XiaomiParseResult::TYPE_GCLS002;
result.name = "GCLS002";
} else if ((raw[2] == 0x5b) && (raw[3] == 0x04)) { // rectangular body, e-ink display
} else if (device_uuid == 0x045b) { // rectangular body, e-ink display
result.type = XiaomiParseResult::TYPE_LYWSD02;
result.name = "LYWSD02";
} else if ((raw[2] == 0x0a) && (raw[3] == 0x04)) { // Mosquito Repellent Smart Version
} else if (device_uuid == 0x040a) { // Mosquito Repellent Smart Version
result.type = XiaomiParseResult::TYPE_WX08ZM;
result.name = "WX08ZM";
} else if ((raw[2] == 0x76) && (raw[3] == 0x05)) { // Cleargrass (Qingping) alarm clock, segment LCD
} else if (device_uuid == 0x0576) { // Cleargrass (Qingping) alarm clock, segment LCD
result.type = XiaomiParseResult::TYPE_CGD1;
result.name = "CGD1";
} else if ((raw[2] == 0x6F) && (raw[3] == 0x06)) { // Cleargrass (Qingping) Temp & RH Lite
} else if (device_uuid == 0x066F) { // Cleargrass (Qingping) Temp & RH Lite
result.type = XiaomiParseResult::TYPE_CGDK2;
result.name = "CGDK2";
} else if ((raw[2] == 0x5b) && (raw[3] == 0x05)) { // small square body, segment LCD, encrypted
} else if (device_uuid == 0x055b) { // small square body, segment LCD, encrypted
result.type = XiaomiParseResult::TYPE_LYWSD03MMC;
result.name = "LYWSD03MMC";
} else if ((raw[2] == 0xf6) && (raw[3] == 0x07)) { // Xiaomi-Yeelight BLE nightlight
} else if (device_uuid == 0x07f6) { // Xiaomi-Yeelight BLE nightlight
result.type = XiaomiParseResult::TYPE_MJYD02YLA;
result.name = "MJYD02YLA";
if (raw.size() == 19)
result.raw_offset -= 6;
} else if ((raw[2] == 0xd3) && (raw[3] == 0x06)) { // rectangular body, e-ink display with alarm
} else if (device_uuid == 0x06d3) { // rectangular body, e-ink display with alarm
result.type = XiaomiParseResult::TYPE_MHOC303;
result.name = "MHOC303";
} else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display
} else if (device_uuid == 0x0387) { // square body, e-ink display
result.type = XiaomiParseResult::TYPE_MHOC401;
result.name = "MHOC401";
} else if ((raw[2] == 0x83) && (raw[3] == 0x0A)) { // Qingping-branded, motion & ambient light sensor
} else if (device_uuid == 0x0A83) { // Qingping-branded, motion & ambient light sensor
result.type = XiaomiParseResult::TYPE_CGPR1;
result.name = "CGPR1";
if (raw.size() == 19)
result.raw_offset -= 6;
} else if (device_uuid == 0x0A8D) { // Xiaomi Mi Motion Sensor 2
result.type = XiaomiParseResult::TYPE_RTCGQ02LM;
result.name = "RTCGQ02LM";
if (raw.size() == 19)
result.raw_offset -= 6;
} else {
ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes.");
return {};
@@ -343,6 +357,9 @@ bool report_xiaomi_results(const optional<XiaomiParseResult> &result, const std:
if (result->is_light.has_value()) {
ESP_LOGD(TAG, " Light: %s", (*result->is_light) ? "on" : "off");
}
if (result->button_press.has_value()) {
ESP_LOGD(TAG, " Button: %s", (*result->button_press) ? "pressed" : "");
}
return true;
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/component.h"
#ifdef USE_ESP32
@@ -25,7 +25,8 @@ struct XiaomiParseResult {
TYPE_MJYD02YLA,
TYPE_MHOC303,
TYPE_MHOC401,
TYPE_CGPR1
TYPE_CGPR1,
TYPE_RTCGQ02LM,
} type;
std::string name;
optional<float> temperature;
@@ -40,6 +41,7 @@ struct XiaomiParseResult {
optional<bool> is_active;
optional<bool> has_motion;
optional<bool> is_light;
optional<bool> button_press;
bool has_data; // 0x40
bool has_capability; // 0x20
bool has_encryption; // 0x08
@@ -61,7 +63,7 @@ struct XiaomiAESVector {
size_t ivsize;
};
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result);
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data);
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address);

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY
AUTO_LOAD = ["xiaomi_ble"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32_ble_tracker"]
MULTI_CONF = True
xiaomi_rtcgq02lm_ns = cg.esphome_ns.namespace("xiaomi_rtcgq02lm")
XiaomiRTCGQ02LM = xiaomi_rtcgq02lm_ns.class_(
"XiaomiRTCGQ02LM", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(XiaomiRTCGQ02LM),
cv.Required(CONF_BINDKEY): cv.bind_key,
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
cg.add(var.set_bindkey(config[CONF_BINDKEY]))

View File

@@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
CONF_LIGHT,
CONF_MOTION,
CONF_TIMEOUT,
DEVICE_CLASS_LIGHT,
DEVICE_CLASS_MOTION,
CONF_ID,
)
from esphome.core import TimePeriod
from . import XiaomiRTCGQ02LM
DEPENDENCIES = ["xiaomi_rtcgq02lm"]
CONF_BUTTON = "button"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM),
cv.Optional(CONF_MOTION): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_MOTION
).extend(
{
cv.Optional(CONF_TIMEOUT, default="5s"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(max=TimePeriod(milliseconds=65535)),
),
}
),
cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_LIGHT
),
cv.Optional(CONF_BUTTON): binary_sensor.binary_sensor_schema().extend(
{
cv.Optional(CONF_TIMEOUT, default="200ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(max=TimePeriod(milliseconds=65535)),
),
}
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_MOTION in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_MOTION])
cg.add(parent.set_motion(sens))
cg.add(parent.set_motion_timeout(config[CONF_MOTION][CONF_TIMEOUT]))
if CONF_LIGHT in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_LIGHT])
cg.add(parent.set_light(sens))
if CONF_BUTTON in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_BUTTON])
cg.add(parent.set_button(sens))
cg.add(parent.set_button_timeout(config[CONF_BUTTON][CONF_TIMEOUT]))

View File

@@ -0,0 +1,37 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_BATTERY_LEVEL,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
CONF_ID,
DEVICE_CLASS_BATTERY,
)
from . import XiaomiRTCGQ02LM
DEPENDENCIES = ["xiaomi_rtcgq02lm"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_BATTERY_LEVEL in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(parent.set_battery_level(sens))

View File

@@ -0,0 +1,91 @@
#include "xiaomi_rtcgq02lm.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace xiaomi_rtcgq02lm {
static const char *const TAG = "xiaomi_rtcgq02lm";
void XiaomiRTCGQ02LM::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM");
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Motion", this->motion_);
LOG_BINARY_SENSOR(" ", "Light", this->light_);
LOG_BINARY_SENSOR(" ", "Button", this->button_);
#endif
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
#endif
}
bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (device.address_uint64() != this->address_) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
bool success = false;
for (auto &service_data : device.get_service_datas()) {
auto res = xiaomi_ble::parse_xiaomi_header(service_data);
if (!res.has_value()) {
continue;
}
if (res->is_duplicate) {
continue;
}
if (res->has_encryption &&
(!(xiaomi_ble::decrypt_xiaomi_payload(const_cast<std::vector<uint8_t> &>(service_data.data), this->bindkey_,
this->address_)))) {
continue;
}
if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) {
continue;
}
if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) {
continue;
}
#ifdef USE_BINARY_SENSOR
if (res->has_motion.has_value() && this->motion_ != nullptr) {
this->motion_->publish_state(*res->has_motion);
this->set_timeout("motion_timeout", this->motion_timeout_,
[this, res]() { this->motion_->publish_state(false); });
}
if (res->is_light.has_value() && this->light_ != nullptr)
this->light_->publish_state(*res->is_light);
if (res->button_press.has_value() && this->button_ != nullptr) {
this->button_->publish_state(*res->button_press);
this->set_timeout("button_timeout", this->button_timeout_,
[this, res]() { this->button_->publish_state(false); });
}
#endif
#ifdef USE_SENSOR
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
this->battery_level_->publish_state(*res->battery_level);
#endif
success = true;
}
return success;
}
void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) {
memset(bindkey_, 0, 16);
if (bindkey.size() != 32) {
return;
}
char temp[3] = {0};
for (int i = 0; i < 16; i++) {
strncpy(temp, &(bindkey.c_str()[i * 2]), 2);
bindkey_[i] = std::strtoul(temp, nullptr, 16);
}
}
} // namespace xiaomi_rtcgq02lm
} // namespace esphome
#endif

View File

@@ -0,0 +1,61 @@
#pragma once
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/defines.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/components/xiaomi_ble/xiaomi_ble.h"
#include "esphome/core/component.h"
#ifdef USE_ESP32
namespace esphome {
namespace xiaomi_rtcgq02lm {
class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
public:
void set_address(uint64_t address) { address_ = address; };
void set_bindkey(const std::string &bindkey);
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
#ifdef USE_BINARY_SENSOR
void set_motion(binary_sensor::BinarySensor *motion) { this->motion_ = motion; }
void set_motion_timeout(uint16_t timeout) { this->motion_timeout_ = timeout; }
void set_light(binary_sensor::BinarySensor *light) { this->light_ = light; }
void set_button(binary_sensor::BinarySensor *button) { this->button_ = button; }
void set_button_timeout(uint16_t timeout) { this->button_timeout_ = timeout; }
#endif
#ifdef USE_SENSOR
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
#endif
protected:
uint64_t address_;
uint8_t bindkey_[16];
#ifdef USE_BINARY_SENSOR
uint16_t motion_timeout_;
uint16_t button_timeout_;
binary_sensor::BinarySensor *motion_{nullptr};
binary_sensor::BinarySensor *light_{nullptr};
binary_sensor::BinarySensor *button_{nullptr};
#endif
#ifdef USE_SENSOR
sensor::Sensor *battery_level_{nullptr};
#endif
};
} // namespace xiaomi_rtcgq02lm
} // namespace esphome
#endif

View File

@@ -161,6 +161,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
# type: (ConfigPath) -> Optional[vol.Invalid]
for err in self.errors:
if self.get_deepest_path(err.path) == path:
self.errors.remove(err)
return err
return None
@@ -647,7 +648,7 @@ class FinalValidateValidationStep(ConfigValidationStep):
fv.full_config.reset(token)
def validate_config(config, command_line_substitutions):
def validate_config(config, command_line_substitutions) -> Config:
result = Config()
loader.clear_component_meta_finders()
@@ -734,9 +735,6 @@ def validate_config(config, command_line_substitutions):
result.add_validation_step(LoadValidationStep(key, config[key]))
result.run_validation_steps()
if result.errors:
return result
for domain, conf in config.items():
result.add_validation_step(LoadValidationStep(domain, conf))
result.add_validation_step(IDPassValidationStep())
@@ -991,5 +989,10 @@ def read_config(command_line_substitutions):
errstr += f" {errline}"
safe_print(errstr)
safe_print(indent(dump_dict(res, path)[0]))
for err in res.errors:
safe_print(color(Fore.BOLD_RED, err.msg))
safe_print("")
return None
return OrderedDict(res)

View File

@@ -58,7 +58,7 @@ from esphome.core import (
)
from esphome.helpers import list_starts_with, add_class_to_obj
from esphome.jsonschema import (
jschema_composite,
jschema_list,
jschema_extractor,
jschema_registry,
jschema_typed,
@@ -327,7 +327,7 @@ def boolean(value):
)
@jschema_composite
@jschema_list
def ensure_list(*validators):
"""Validate this configuration option to be a list.
@@ -494,7 +494,11 @@ def templatable(other_validators):
"""
schema = Schema(other_validators)
@jschema_extractor("templatable")
def validator(value):
# pylint: disable=comparison-with-callable
if value == jschema_extractor:
return other_validators
if isinstance(value, Lambda):
return returning_lambda(value)
if isinstance(other_validators, dict):
@@ -1426,7 +1430,6 @@ class SplitDefault(Optional):
esp32=vol.UNDEFINED,
esp32_arduino=vol.UNDEFINED,
esp32_idf=vol.UNDEFINED,
host=vol.UNDEFINED,
):
super().__init__(key)
self._esp8266_default = vol.default_factory(esp8266)
@@ -1436,7 +1439,6 @@ class SplitDefault(Optional):
self._esp32_idf_default = vol.default_factory(
esp32_idf if esp32 is vol.UNDEFINED else esp32
)
self._host_default = vol.default_factory(host)
@property
def default(self):
@@ -1446,8 +1448,6 @@ class SplitDefault(Optional):
return self._esp32_arduino_default
if CORE.is_esp32 and CORE.using_esp_idf:
return self._esp32_idf_default
if CORE.is_host:
return self._host_default
raise NotImplementedError
@default.setter
@@ -1550,7 +1550,7 @@ def validate_registry(name, registry):
return ensure_list(validate_registry_entry(name, registry))
@jschema_composite
@jschema_list
def maybe_simple_value(*validators, **kwargs):
key = kwargs.pop("key", CONF_VALUE)
validator = All(*validators)

View File

@@ -1,16 +1,13 @@
"""Constants used by esphome."""
__version__ = "2022.3.0-dev"
__version__ = "2022.4.0b1"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
TARGET_FRAMEWORKS = ["arduino", "esp-idf"]
PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266"
PLATFORM_HOST = "host"
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_HOST]
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
@@ -647,6 +644,7 @@ CONF_STEP_MODE = "step_mode"
CONF_STEP_PIN = "step_pin"
CONF_STOP = "stop"
CONF_STOP_ACTION = "stop_action"
CONF_STORE_BASELINE = "store_baseline"
CONF_SUBNET = "subnet"
CONF_SUBSTITUTIONS = "substitutions"
CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action"
@@ -683,6 +681,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic"
CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic"
CONF_TEMPERATURE = "temperature"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONF_TEMPERATURE_STEP = "temperature_step"
CONF_TEXT_SENSORS = "text_sensors"
CONF_THEN = "then"

View File

@@ -593,10 +593,6 @@ class EsphomeCore:
def is_esp32(self):
return self.target_platform == "esp32"
@property
def is_host(self):
return self.target_platform == "host"
@property
def target_framework(self):
return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK]

View File

@@ -1,16 +1,18 @@
#include "util.h"
#include "api_server.h"
#include "user_services.h"
#include "esphome/core/log.h"
#include "component_iterator.h"
#include "esphome/core/application.h"
namespace esphome {
namespace api {
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#include "esphome/components/api/user_services.h"
#endif
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
void ComponentIterator::begin() {
namespace esphome {
void ComponentIterator::begin(bool include_internal) {
this->state_ = IteratorState::BEGIN;
this->at_ = 0;
this->include_internal_ = include_internal;
}
void ComponentIterator::advance() {
bool advance_platform = false;
@@ -32,7 +34,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *binary_sensor = App.get_binary_sensors()[this->at_];
if (binary_sensor->is_internal()) {
if (binary_sensor->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -47,7 +49,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *cover = App.get_covers()[this->at_];
if (cover->is_internal()) {
if (cover->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -62,7 +64,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *fan = App.get_fans()[this->at_];
if (fan->is_internal()) {
if (fan->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -77,7 +79,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *light = App.get_lights()[this->at_];
if (light->is_internal()) {
if (light->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -92,7 +94,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *sensor = App.get_sensors()[this->at_];
if (sensor->is_internal()) {
if (sensor->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -107,7 +109,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *a_switch = App.get_switches()[this->at_];
if (a_switch->is_internal()) {
if (a_switch->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -122,7 +124,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *button = App.get_buttons()[this->at_];
if (button->is_internal()) {
if (button->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -137,7 +139,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *text_sensor = App.get_text_sensors()[this->at_];
if (text_sensor->is_internal()) {
if (text_sensor->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -146,20 +148,22 @@ void ComponentIterator::advance() {
}
break;
#endif
#ifdef USE_API
case IteratorState ::SERVICE:
if (this->at_ >= this->server_->get_user_services().size()) {
if (this->at_ >= api::global_api_server->get_user_services().size()) {
advance_platform = true;
} else {
auto *service = this->server_->get_user_services()[this->at_];
auto *service = api::global_api_server->get_user_services()[this->at_];
success = this->on_service(service);
}
break;
#endif
#ifdef USE_ESP32_CAMERA
case IteratorState::CAMERA:
if (esp32_camera::global_esp32_camera == nullptr) {
advance_platform = true;
} else {
if (esp32_camera::global_esp32_camera->is_internal()) {
if (esp32_camera::global_esp32_camera->is_internal() && !this->include_internal_) {
advance_platform = success = true;
break;
} else {
@@ -174,7 +178,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *climate = App.get_climates()[this->at_];
if (climate->is_internal()) {
if (climate->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -189,7 +193,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *number = App.get_numbers()[this->at_];
if (number->is_internal()) {
if (number->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -204,7 +208,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *select = App.get_selects()[this->at_];
if (select->is_internal()) {
if (select->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -219,7 +223,7 @@ void ComponentIterator::advance() {
advance_platform = true;
} else {
auto *a_lock = App.get_locks()[this->at_];
if (a_lock->is_internal()) {
if (a_lock->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
@@ -244,10 +248,10 @@ void ComponentIterator::advance() {
}
bool ComponentIterator::on_end() { return true; }
bool ComponentIterator::on_begin() { return true; }
bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; }
#ifdef USE_API
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
#endif
#ifdef USE_ESP32_CAMERA
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; }
#endif
} // namespace api
} // namespace esphome

View File

@@ -1,23 +1,24 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32_CAMERA
#include "esphome/components/esp32_camera/esp32_camera.h"
#endif
namespace esphome {
namespace api {
class APIServer;
#ifdef USE_API
namespace api {
class UserServiceDescriptor;
} // namespace api
#endif
class ComponentIterator {
public:
ComponentIterator(APIServer *server);
void begin();
void begin(bool include_internal = false);
void advance();
virtual bool on_begin();
#ifdef USE_BINARY_SENSOR
@@ -44,7 +45,9 @@ class ComponentIterator {
#ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif
virtual bool on_service(UserServiceDescriptor *service);
#ifdef USE_API
virtual bool on_service(api::UserServiceDescriptor *service);
#endif
#ifdef USE_ESP32_CAMERA
virtual bool on_camera(esp32_camera::ESP32Camera *camera);
#endif
@@ -90,7 +93,9 @@ class ComponentIterator {
#ifdef USE_TEXT_SENSOR
TEXT_SENSOR,
#endif
#ifdef USE_API
SERVICE,
#endif
#ifdef USE_ESP32_CAMERA
CAMERA,
#endif
@@ -109,9 +114,7 @@ class ComponentIterator {
MAX,
} state_{IteratorState::NONE};
size_t at_{0};
APIServer *server_;
bool include_internal_{false};
};
} // namespace api
} // namespace esphome

View File

@@ -29,6 +29,7 @@
#define USE_LOCK
#define USE_LOGGER
#define USE_MDNS
#define USE_MQTT
#define USE_NUMBER
#define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK
@@ -40,6 +41,7 @@
#define USE_SWITCH
#define USE_TEXT_SENSOR
#define USE_TIME
#define USE_TOUCHSCREEN
#define USE_UART_DEBUGGER
#define USE_WIFI
@@ -48,13 +50,17 @@
#define USE_CAPTIVE_PORTAL
#define USE_JSON
#define USE_NEXTION_TFT_UPLOAD
#define USE_MQTT
#define USE_PROMETHEUS
#define USE_WEBSERVER
#define USE_WEBSERVER_PORT 80 // NOLINT
#define USE_WIFI_WPA2_EAP
#endif
// IDF-specific feature flags
#ifdef USE_ESP_IDF
#define USE_MQTT_IDF_ENQUEUE
#endif
// ESP32-specific feature flags
#ifdef USE_ESP32
#define USE_ESP32_BLE_CLIENT
@@ -83,10 +89,6 @@
#define USE_SOCKET_IMPL_LWIP_TCP
#endif
#ifdef USE_HOST
#define USE_SOCKET_IMPL_BSD_SOCKETS
#endif
// Disabled feature flags
//#define USE_BSEC // Requires a library with proprietary license.

View File

@@ -21,10 +21,6 @@
#include "esp_system.h"
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#elif defined(USE_HOST)
#include <cstdio>
#include <random>
#include <limits>
#endif
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
@@ -80,11 +76,6 @@ uint32_t random_uint32() {
return esp_random();
#elif defined(USE_ESP8266)
return os_random();
#elif defined(USE_HOST)
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
return dist(rng);
#else
#error "No random source available for this configuration."
#endif
@@ -96,19 +87,6 @@ bool random_bytes(uint8_t *data, size_t len) {
return true;
#elif defined(USE_ESP8266)
return os_get_random(data, len) == 0;
#elif defined(USE_HOST)
FILE *fp = fopen("/dev/urandom", "r");
if (fp == nullptr) {
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
exit(1);
}
size_t read = fread(data, 1, len, fp);
if (read != len) {
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
exit(1);
}
fclose(fp);
return true
#else
#error "No random source available for this configuration."
#endif
@@ -235,6 +213,25 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) {
}
std::string format_hex_pretty(const std::vector<uint8_t> &data) { return format_hex_pretty(data.data(), data.size()); }
std::string format_hex_pretty(const uint16_t *data, size_t length) {
if (length == 0)
return "";
std::string ret;
ret.resize(5 * length - 1);
for (size_t i = 0; i < length; i++) {
ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4);
ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F);
if (i != length - 1)
ret[5 * i + 2] = '.';
}
if (length > 4)
return ret + " (" + to_string(length) + ")";
return ret;
}
std::string format_hex_pretty(const std::vector<uint16_t> &data) { return format_hex_pretty(data.data(), data.size()); }
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
if (on == nullptr && strcasecmp(str, "on") == 0)
return PARSE_ON;

View File

@@ -173,6 +173,10 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui
return (static_cast<uint32_t>(byte1) << 24) | (static_cast<uint32_t>(byte2) << 16) |
(static_cast<uint32_t>(byte3) << 8) | (static_cast<uint32_t>(byte4));
}
/// Encode a 24-bit value given three bytes in most to least significant byte order.
constexpr uint32_t encode_uint24(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
return ((static_cast<uint32_t>(byte1) << 16) | (static_cast<uint32_t>(byte2) << 8) | (static_cast<uint32_t>(byte3)));
}
/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T).
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
@@ -386,8 +390,12 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
std::string format_hex_pretty(const uint8_t *data, size_t length);
/// Format the word array \p data of length \p len in pretty-printed, human-readable hex.
std::string format_hex_pretty(const uint16_t *data, size_t length);
/// Format the vector \p data in pretty-printed, human-readable hex.
std::string format_hex_pretty(const std::vector<uint8_t> &data);
/// Format the vector \p data in pretty-printed, human-readable hex.
std::string format_hex_pretty(const std::vector<uint16_t> &data);
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
val = convert_big_endian(val);

View File

@@ -1,7 +1,7 @@
"""Helpers to retrieve schema from voluptuous validators.
These are a helper decorators to help get schema from some
components which uses volutuous in a way where validation
components which uses voluptuous in a way where validation
is hidden in local functions
These decorators should not modify at all what the functions
originally do.
@@ -24,7 +24,7 @@ def jschema_extractor(validator_name):
if EnableJsonSchemaCollect:
def decorator(func):
hidden_schemas[str(func)] = validator_name
hidden_schemas[repr(func)] = validator_name
return func
return decorator
@@ -41,7 +41,7 @@ def jschema_extended(func):
def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
assert len(args) == 2
extended_schemas[str(ret)] = args
extended_schemas[repr(ret)] = args
return ret
return decorate
@@ -49,13 +49,13 @@ def jschema_extended(func):
return func
def jschema_composite(func):
def jschema_list(func):
if EnableJsonSchemaCollect:
def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
# args length might be 2, but 2nd is always validator
list_schemas[str(ret)] = args
list_schemas[repr(ret)] = args
return ret
return decorate
@@ -67,7 +67,7 @@ def jschema_registry(registry):
if EnableJsonSchemaCollect:
def decorator(func):
registry_schemas[str(func)] = registry
registry_schemas[repr(func)] = registry
return func
return decorator
@@ -83,7 +83,7 @@ def jschema_typed(func):
def decorate(*args, **kwargs):
ret = func(*args, **kwargs)
typed_schemas[str(ret)] = (args, kwargs)
typed_schemas[repr(ret)] = (args, kwargs)
return ret
return decorate

View File

@@ -62,9 +62,7 @@ lib_deps =
glmnet/Dsmr@0.5 ; dsmr
rweather/Crypto@0.2.0 ; dsmr
dudanov/MideaUART@1.1.8 ; midea
; PIO isn't update releases correctly, see:
; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
https://github.com/ToniA/arduino-heatpumpir.git#1.0.18 ; heatpumpir
tonia/HeatpumpIR@1.0.20 ; heatpumpir
build_flags =
${common.build_flags}
-DUSE_ARDUINO
@@ -220,13 +218,3 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
build_flags =
${common:esp32-idf.build_flags}
${flags:clangtidy.build_flags}
[env:host]
extends = common
platform = platformio/native
lib_deps =
esphome/noise-c@0.1.1 ; used by api
build_flags =
${common.build_flags}
-DUSE_HOST
${flags:runtime.build_flags}

View File

@@ -1,17 +1,17 @@
voluptuous==0.12.2
voluptuous==0.13.1
PyYAML==6.0
paho-mqtt==1.6.1
colorama==0.4.4
tornado==6.1
tzlocal==4.1 # from time
tzlocal==4.2 # from time
tzdata>=2021.1 # from time
pyserial==3.5
platformio==5.2.5 # When updating platformio, also update Dockerfile
esptool==3.2
click==8.0.3
esphome-dashboard==20220219.0
esptool==3.3
click==8.1.2
esphome-dashboard==20220309.0
aioesphomeapi==10.8.2
zeroconf==0.38.3
zeroconf==0.38.4
# esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

View File

@@ -1,13 +1,13 @@
pylint==2.12.2
pylint==2.13.5
flake8==4.0.1
black==22.1.0
pyupgrade==2.31.0
black==22.3.0
pyupgrade==2.32.0
pre-commit
# Unit tests
pytest==7.0.1
pytest==7.1.1
pytest-cov==3.0.0
pytest-mock==3.7.0
pytest-asyncio==0.18.1
pytest-asyncio==0.18.3
asyncmock==0.4.2
hypothesis==5.49.0

View File

@@ -236,7 +236,7 @@ class Int64Type(TypeInfo):
encode_func = "encode_int64"
def dump(self, name):
o = f'sprintf(buffer, "%ll", {name});\n'
o = f'sprintf(buffer, "%lld", {name});\n'
o += f"out.append(buffer);"
return o
@@ -249,7 +249,7 @@ class UInt64Type(TypeInfo):
encode_func = "encode_uint64"
def dump(self, name):
o = f'sprintf(buffer, "%ull", {name});\n'
o = f'sprintf(buffer, "%llu", {name});\n'
o += f"out.append(buffer);"
return o
@@ -275,7 +275,7 @@ class Fixed64Type(TypeInfo):
encode_func = "encode_fixed64"
def dump(self, name):
o = f'sprintf(buffer, "%ull", {name});\n'
o = f'sprintf(buffer, "%llu", {name});\n'
o += f"out.append(buffer);"
return o
@@ -417,7 +417,7 @@ class SFixed64Type(TypeInfo):
encode_func = "encode_sfixed64"
def dump(self, name):
o = f'sprintf(buffer, "%ll", {name});\n'
o = f'sprintf(buffer, "%lld", {name});\n'
o += f"out.append(buffer);"
return o
@@ -440,10 +440,10 @@ class SInt64Type(TypeInfo):
cpp_type = "int64_t"
default_value = "0"
decode_varint = "value.as_sint64()"
encode_func = "encode_sin64"
encode_func = "encode_sint64"
def dump(self, name):
o = f'sprintf(buffer, "%ll", {name});\n'
o = f'sprintf(buffer, "%lld", {name});\n'
o += f"out.append(buffer);"
return o
@@ -622,13 +622,13 @@ def build_message_type(desc):
protected_content.insert(0, prot)
if decode_64bit:
decode_64bit.append("default:\n return false;")
o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n"
o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64Bit value) {{\n"
o += " switch (field_id) {\n"
o += indent("\n".join(decode_64bit), " ") + "\n"
o += " }\n"
o += "}\n"
cpp += o
prot = "bool decode_64bit(uint32_t field_id, Proto64bit value) override;"
prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;"
protected_content.insert(0, prot)
o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{"

View File

@@ -70,7 +70,7 @@ def add_definition_array_or_single_object(ref):
def add_core():
from esphome.core.config import CONFIG_SCHEMA
base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema)
base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA)
def add_buses():
@@ -216,7 +216,7 @@ def add_components():
add_module_registries(domain, c.module)
add_module_schemas(domain, c.module)
# need first to iterate all platforms then iteate components
# need first to iterate all platforms then iterate components
# a platform component can have other components as properties,
# e.g. climate components usually have a temperature sensor
@@ -325,7 +325,9 @@ def get_entry(parent_key, vschema):
if DUMP_COMMENTS:
entry[JSC_COMMENT] = "entry: " + parent_key + "/" + str(vschema)
if isinstance(vschema, list):
if isinstance(vschema, dict):
entry = {"what": "is_this"}
elif isinstance(vschema, list):
ref = get_jschema(parent_key + "[]", vschema[0])
entry = {"type": "array", "items": ref}
elif isinstance(vschema, schema_type) and hasattr(vschema, "schema"):
@@ -387,8 +389,10 @@ def get_entry(parent_key, vschema):
v = vschema(None)
if isinstance(v, ID):
if v.type.base != "script::Script" and (
v.type.inherits_from(Trigger) or v.type == Automation
if (
v.type.base != "script::Script"
and v.type.base != "switch_::Switch"
and (v.type.inherits_from(Trigger) or v.type == Automation)
):
return None
entry = {"type": "string", "id_type": v.type.base}
@@ -410,6 +414,8 @@ def default_schema():
def is_default_schema(jschema):
if jschema is None:
return False
if is_ref(jschema):
jschema = unref(jschema)
if not jschema:
@@ -425,6 +431,9 @@ def get_jschema(path, vschema, create_return_ref=True):
jschema = convert_schema(path, vschema)
if jschema is None:
return None
if is_ref(jschema):
# this can happen when returned extended
# schemas where all properties found in previous extended schema
@@ -450,6 +459,9 @@ def get_schema_str(vschema):
def create_ref(name, vschema, jschema):
if jschema is None:
raise ValueError("Cannot create a ref with null jschema for " + name)
if name in schema_names:
raise ValueError("Not supported")
@@ -523,6 +535,15 @@ def convert_schema(path, vschema, un_extend=True):
extended = ejs.extended_schemas.get(str(vschema))
if extended:
lhs = get_jschema(path, extended[0], False)
# The midea actions are extending an empty schema (resulted in the templatize not templatizing anything)
# this causes a recursion in that this extended looks the same in extended schema as the extended[1]
if ejs.extended_schemas.get(str(vschema)) == ejs.extended_schemas.get(
str(extended[1])
):
assert path.startswith("midea_ac")
return convert_schema(path, extended[1], False)
rhs = get_jschema(path, extended[1], False)
# check if we are not merging properties which are already in base component
@@ -567,6 +588,8 @@ def convert_schema(path, vschema, un_extend=True):
# we should take the valid schema,
# commonly all is used to validate a schema, and then a function which
# is not a schema es also given, get_schema will then return a default_schema()
if v == dict:
continue # this is a dict in the SCHEMA of packages
val_schema = get_jschema(path, v, False)
if is_default_schema(val_schema):
if not output:
@@ -673,6 +696,11 @@ def add_pin_registry():
for mode in ("INPUT", "OUTPUT"):
schema_name = f"PIN.GPIO_FULL_{mode}_PIN_SCHEMA"
# TODO: get pin definitions properly
if schema_name not in definitions:
definitions[schema_name] = {"type": ["object", "null"], JSC_PROPERTIES: {}}
internal = definitions[schema_name]
definitions[schema_name]["additionalItems"] = False
definitions[f"PIN.{mode}_INTERNAL"] = internal
@@ -683,12 +711,11 @@ def add_pin_registry():
definitions[schema_name] = {"oneOf": schemas, "type": ["string", "object"]}
for k, v in pin_registry.items():
pin_jschema = get_jschema(
f"PIN.{mode}_" + k, v[1][0 if mode == "OUTPUT" else 1]
)
if unref(pin_jschema):
pin_jschema["required"] = [k]
schemas.append(pin_jschema)
if isinstance(v[1], vol.validators.All):
pin_jschema = get_jschema(f"PIN.{mode}_" + k, v[1])
if unref(pin_jschema):
pin_jschema["required"] = [k]
schemas.append(pin_jschema)
def dump_schema():
@@ -730,9 +757,9 @@ def dump_schema():
cv.valid_name,
cv.hex_int,
cv.hex_int_range,
pins.output_pin,
pins.input_pin,
pins.input_pullup_pin,
pins.gpio_output_pin_schema,
pins.gpio_input_pin_schema,
pins.gpio_input_pullup_pin_schema,
cv.float_with_unit,
cv.subscribe_topic,
cv.publish_topic,
@@ -753,12 +780,12 @@ def dump_schema():
for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_schema]:
schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_input_pin_schema, pins.input_pin]:
for v in [pins.internal_gpio_input_pin_schema, pins.gpio_input_pin_schema]:
schema_registry[v] = get_ref("PIN.INPUT_INTERNAL")
for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]:
schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA")
for v in [pins.internal_gpio_output_pin_schema, pins.output_pin]:
for v in [pins.internal_gpio_output_pin_schema, pins.gpio_output_pin_schema]:
schema_registry[v] = get_ref("PIN.OUTPUT_INTERNAL")
add_module_schemas("CONFIG", cv)

View File

@@ -0,0 +1,813 @@
import inspect
import json
import argparse
from operator import truediv
import os
import voluptuous as vol
# NOTE: Cannot import other esphome components globally as a modification in jsonschema
# is needed before modules are loaded
import esphome.jsonschema as ejs
ejs.EnableJsonSchemaCollect = True
# schema format:
# Schemas are splitted in several files in json format, one for core stuff, one for each platform (sensor, binary_sensor, etc) and
# one for each component (dallas, sim800l, etc.) component can have schema for root component/hub and also for platform component,
# e.g. dallas has hub component which has pin and then has the sensor platform which has sensor name, index, etc.
# When files are loaded they are merged in a single object.
# The root format is
S_CONFIG_VAR = "config_var"
S_CONFIG_VARS = "config_vars"
S_CONFIG_SCHEMA = "CONFIG_SCHEMA"
S_COMPONENT = "component"
S_COMPONENTS = "components"
S_PLATFORMS = "platforms"
S_SCHEMA = "schema"
S_SCHEMAS = "schemas"
S_EXTENDS = "extends"
S_TYPE = "type"
S_NAME = "name"
parser = argparse.ArgumentParser()
parser.add_argument(
"--output-path", default=".", help="Output path", type=os.path.abspath
)
args = parser.parse_args()
DUMP_RAW = False
DUMP_UNKNOWN = False
DUMP_PATH = False
JSON_DUMP_PRETTY = True
# store here dynamic load of esphome components
components = {}
schema_core = {}
# output is where all is built
output = {"core": schema_core}
# The full generated output is here here
schema_full = {"components": output}
# A string, string map, key is the str(schema) and value is
# a tuple, first element is the schema reference and second is the schema path given, the schema reference is needed to test as different schemas have same key
known_schemas = {}
solve_registry = []
def get_component_names():
# return [
# "esphome",
# "esp32",
# "esp8266",
# "logger",
# "sensor",
# "remote_receiver",
# "binary_sensor",
# ]
from esphome.loader import CORE_COMPONENTS_PATH
component_names = ["esphome", "sensor"]
for d in os.listdir(CORE_COMPONENTS_PATH):
if not d.startswith("__") and os.path.isdir(
os.path.join(CORE_COMPONENTS_PATH, d)
):
if d not in component_names:
component_names.append(d)
return component_names
def load_components():
from esphome.config import get_component
for domain in get_component_names():
components[domain] = get_component(domain)
load_components()
# Import esphome after loading components (so schema is tracked)
# pylint: disable=wrong-import-position
import esphome.core as esphome_core
import esphome.config_validation as cv
from esphome import automation
from esphome import pins
from esphome.components import remote_base
from esphome.const import CONF_TYPE
from esphome.loader import get_platform
from esphome.helpers import write_file_if_changed
from esphome.util import Registry
# pylint: enable=wrong-import-position
def write_file(name, obj):
full_path = os.path.join(args.output_path, name + ".json")
if JSON_DUMP_PRETTY:
json_str = json.dumps(obj, indent=2)
else:
json_str = json.dumps(obj, separators=(",", ":"))
write_file_if_changed(full_path, json_str)
print(f"Wrote {full_path}")
def register_module_schemas(key, module, manifest=None):
for name, schema in module_schemas(module):
register_known_schema(key, name, schema)
if (
manifest and manifest.multi_conf and S_CONFIG_SCHEMA in output[key][S_SCHEMAS]
): # not sure about 2nd part of the if, might be useless config (e.g. as3935)
output[key][S_SCHEMAS][S_CONFIG_SCHEMA]["is_list"] = True
def register_known_schema(module, name, schema):
if module not in output:
output[module] = {S_SCHEMAS: {}}
config = convert_config(schema, f"{module}/{name}")
if S_TYPE not in config:
print(f"Config var without type: {module}.{name}")
output[module][S_SCHEMAS][name] = config
repr_schema = repr(schema)
if repr_schema in known_schemas:
schema_info = known_schemas[repr_schema]
schema_info.append((schema, f"{module}.{name}"))
else:
known_schemas[repr_schema] = [(schema, f"{module}.{name}")]
def module_schemas(module):
# This should yield elements in order so extended schemas are resolved properly
# To do this we check on the source code where the symbol is seen first. Seems to work.
try:
module_str = inspect.getsource(module)
except TypeError:
# improv
module_str = ""
except OSError:
# some empty __init__ files
module_str = ""
schemas = {}
for m_attr_name in dir(module):
m_attr_obj = getattr(module, m_attr_name)
if isConvertibleSchema(m_attr_obj):
schemas[module_str.find(m_attr_name)] = [m_attr_name, m_attr_obj]
for pos in sorted(schemas.keys()):
yield schemas[pos]
found_registries = {}
# Pin validators keys are the functions in pin which validate the pins
pin_validators = {}
def add_pin_validators():
for m_attr_name in dir(pins):
if "gpio" in m_attr_name:
s = pin_validators[repr(getattr(pins, m_attr_name))] = {}
if "schema" in m_attr_name:
s["schema"] = True # else is just number
if "internal" in m_attr_name:
s["internal"] = True
if "input" in m_attr_name:
s["modes"] = ["input"]
elif "output" in m_attr_name:
s["modes"] = ["output"]
else:
s["modes"] = []
if "pullup" in m_attr_name:
s["modes"].append("pullup")
from esphome.components.adc import sensor as adc_sensor
pin_validators[repr(adc_sensor.validate_adc_pin)] = {
"internal": True,
"modes": ["input"],
}
def add_module_registries(domain, module):
for attr_name in dir(module):
attr_obj = getattr(module, attr_name)
if isinstance(attr_obj, Registry):
if attr_obj == automation.ACTION_REGISTRY:
reg_type = "action"
reg_domain = "core"
found_registries[repr(attr_obj)] = reg_type
elif attr_obj == automation.CONDITION_REGISTRY:
reg_type = "condition"
reg_domain = "core"
found_registries[repr(attr_obj)] = reg_type
else: # attr_name == "FILTER_REGISTRY":
reg_domain = domain
reg_type = attr_name.partition("_")[0].lower()
found_registries[repr(attr_obj)] = f"{domain}.{reg_type}"
for name in attr_obj.keys():
if "." not in name:
reg_entry_name = name
else:
parts = name.split(".")
if len(parts) == 2:
reg_domain = parts[0]
reg_entry_name = parts[1]
else:
reg_domain = ".".join([parts[1], parts[0]])
reg_entry_name = parts[2]
if reg_domain not in output:
output[reg_domain] = {}
if reg_type not in output[reg_domain]:
output[reg_domain][reg_type] = {}
output[reg_domain][reg_type][reg_entry_name] = convert_config(
attr_obj[name].schema, f"{reg_domain}/{reg_type}/{reg_entry_name}"
)
# print(f"{domain} - {attr_name} - {name}")
def do_pins():
# do pin registries
pins_providers = schema_core["pins"] = []
for pin_registry in pins.PIN_SCHEMA_REGISTRY:
s = convert_config(
pins.PIN_SCHEMA_REGISTRY[pin_registry][1], f"pins/{pin_registry}"
)
if pin_registry not in output:
output[pin_registry] = {} # mcp23xxx does not create a component yet
output[pin_registry]["pin"] = s
pins_providers.append(pin_registry)
def do_esp32():
import esphome.components.esp32.boards as esp32_boards
setEnum(
output["esp32"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
list(esp32_boards.BOARD_TO_VARIANT.keys()),
)
def do_esp8266():
import esphome.components.esp8266.boards as esp8266_boards
setEnum(
output["esp8266"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
list(esp8266_boards.ESP8266_BOARD_PINS.keys()),
)
def fix_remote_receiver():
output["remote_receiver.binary_sensor"]["schemas"]["CONFIG_SCHEMA"] = {
"type": "schema",
"schema": {
"extends": ["binary_sensor.BINARY_SENSOR_SCHEMA", "core.COMPONENT_SCHEMA"],
"config_vars": output["remote_base"]["binary"],
},
}
def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=False):
assert (
S_CONFIG_VARS not in config_var and S_EXTENDS not in config_var
) # S_TYPE in cv or "key" in cv or len(cv) == 0
if (
config_var.get(S_TYPE) in ["schema", "trigger", "maybe"]
and S_SCHEMA in config_var
):
schema = config_var[S_SCHEMA]
for k, v in schema.get(S_CONFIG_VARS, {}).items():
if eat_schema:
new_path = path + [S_CONFIG_VARS, k]
else:
new_path = path + ["schema", S_CONFIG_VARS, k]
add_referenced_recursive(referenced_schemas, v, new_path)
for k in schema.get(S_EXTENDS, []):
if k not in referenced_schemas:
referenced_schemas[k] = [path]
else:
if path not in referenced_schemas[k]:
referenced_schemas[k].append(path)
s1 = get_str_path_schema(k)
p = k.split(".")
if len(p) == 3 and path[0] == f"{p[0]}.{p[1]}":
# special case for schema inside platforms
add_referenced_recursive(
referenced_schemas, s1, [path[0], "schemas", p[2]]
)
else:
add_referenced_recursive(
referenced_schemas, s1, [p[0], "schemas", p[1]]
)
elif config_var.get(S_TYPE) == "typed":
for tk, tv in config_var.get("types").items():
add_referenced_recursive(
referenced_schemas,
{
S_TYPE: S_SCHEMA,
S_SCHEMA: tv,
},
path + ["types", tk],
eat_schema=True,
)
def get_str_path_schema(strPath):
parts = strPath.split(".")
if len(parts) > 2:
parts[0] += "." + parts[1]
parts[1] = parts[2]
s1 = output.get(parts[0], {}).get(S_SCHEMAS, {}).get(parts[1], {})
return s1
def pop_str_path_schema(strPath):
parts = strPath.split(".")
if len(parts) > 2:
parts[0] += "." + parts[1]
parts[1] = parts[2]
output.get(parts[0], {}).get(S_SCHEMAS, {}).pop(parts[1])
def get_arr_path_schema(path):
s = output
for x in path:
s = s[x]
return s
def merge(source, destination):
"""
run me with nosetests --with-doctest file.py
>>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
>>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
>>> merge(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
True
"""
for key, value in source.items():
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
merge(value, node)
else:
destination[key] = value
return destination
def shrink():
"""Shrink the extending schemas which has just an end type, e.g. at this point
ota / port is type schema with extended pointing to core.port, this should instead be
type number. core.port is number
This also fixes enums, as they are another schema and they are instead put in the same cv
"""
# referenced_schemas contains a dict, keys are all that are shown in extends: [] arrays, values are lists of paths that are pointing to that extend
# e.g. key: core.COMPONENT_SCHEMA has a lot of paths of config vars which extends this schema
pass_again = True
while pass_again:
pass_again = False
referenced_schemas = {}
for k, v in output.items():
for kv, vv in v.items():
if kv != "pin" and isinstance(vv, dict):
for kvv, vvv in vv.items():
add_referenced_recursive(referenced_schemas, vvv, [k, kv, kvv])
for x, paths in referenced_schemas.items():
if len(paths) == 1:
key_s = get_str_path_schema(x)
arr_s = get_arr_path_schema(paths[0])
# key_s |= arr_s
# key_s.pop(S_EXTENDS)
pass_again = True
if S_SCHEMA in arr_s:
if S_EXTENDS in arr_s[S_SCHEMA]:
arr_s[S_SCHEMA].pop(S_EXTENDS)
else:
print("expected extends here!" + x)
arr_s = merge(key_s, arr_s)
if arr_s[S_TYPE] == "enum":
arr_s.pop(S_SCHEMA)
else:
arr_s.pop(S_EXTENDS)
arr_s |= key_s[S_SCHEMA]
print(x)
# simple types should be spread on each component,
# for enums so far these are logger.is_log_level, cover.validate_cover_state and pulse_counter.sensor.COUNT_MODE_SCHEMA
# then for some reasons sensor filter registry falls here
# then are all simple types, integer and strings
for x, paths in referenced_schemas.items():
key_s = get_str_path_schema(x)
if key_s and key_s[S_TYPE] in ["enum", "registry", "integer", "string"]:
if key_s[S_TYPE] == "registry":
print("Spreading registry: " + x)
for target in paths:
target_s = get_arr_path_schema(target)
assert target_s[S_SCHEMA][S_EXTENDS] == [x]
target_s.pop(S_SCHEMA)
target_s |= key_s
if key_s[S_TYPE] in ["integer", "string"]:
target_s["data_type"] = x.split(".")[1]
# remove this dangling again
pop_str_path_schema(x)
elif not key_s:
for target in paths:
target_s = get_arr_path_schema(target)
assert target_s[S_SCHEMA][S_EXTENDS] == [x]
target_s.pop(S_SCHEMA)
target_s.pop(S_TYPE) # undefined
target_s["data_type"] = x.split(".")[1]
# remove this dangling again
pop_str_path_schema(x)
# remove dangling items (unreachable schemas)
for domain, domain_schemas in output.items():
for schema_name in list(domain_schemas.get(S_SCHEMAS, {}).keys()):
s = f"{domain}.{schema_name}"
if (
not s.endswith("." + S_CONFIG_SCHEMA)
and s not in referenced_schemas.keys()
):
print(f"Removing {s}")
output[domain][S_SCHEMAS].pop(schema_name)
def build_schema():
print("Building schema")
# check esphome was not loaded globally (IDE auto imports)
if len(ejs.extended_schemas) == 0:
raise Exception(
"no data collected. Did you globally import an ESPHome component?"
)
# Core schema
schema_core[S_SCHEMAS] = {}
register_module_schemas("core", cv)
platforms = {}
schema_core[S_PLATFORMS] = platforms
core_components = {}
schema_core[S_COMPONENTS] = core_components
add_pin_validators()
# Load a preview of each component
for domain, manifest in components.items():
if manifest.is_platform_component:
# e.g. sensor, binary sensor, add S_COMPONENTS
# note: S_COMPONENTS is not filled until loaded, e.g.
# if lock: is not used, then we don't need to know about their
# platforms yet.
output[domain] = {S_COMPONENTS: {}, S_SCHEMAS: {}}
platforms[domain] = {}
elif manifest.config_schema is not None:
# e.g. dallas
output[domain] = {S_SCHEMAS: {S_CONFIG_SCHEMA: {}}}
# Generate platforms (e.g. sensor, binary_sensor, climate )
for domain in platforms:
c = components[domain]
register_module_schemas(domain, c.module)
# Generate components
for domain, manifest in components.items():
if domain not in platforms:
if manifest.config_schema is not None:
core_components[domain] = {}
register_module_schemas(domain, manifest.module, manifest)
for platform in platforms:
platform_manifest = get_platform(domain=platform, platform=domain)
if platform_manifest is not None:
output[platform][S_COMPONENTS][domain] = {}
register_module_schemas(
f"{domain}.{platform}", platform_manifest.module
)
# Do registries
add_module_registries("core", automation)
for domain, manifest in components.items():
add_module_registries(domain, manifest.module)
add_module_registries("remote_base", remote_base)
# update props pointing to registries
for reg_config_var in solve_registry:
(registry, config_var) = reg_config_var
config_var[S_TYPE] = "registry"
config_var["registry"] = found_registries[repr(registry)]
do_pins()
do_esp8266()
do_esp32()
fix_remote_receiver()
shrink()
# aggregate components, so all component info is in same file, otherwise we have dallas.json, dallas.sensor.json, etc.
data = {}
for component, component_schemas in output.items():
if "." in component:
key = component.partition(".")[0]
if key not in data:
data[key] = {}
data[key][component] = component_schemas
else:
if component not in data:
data[component] = {}
data[component] |= {component: component_schemas}
# bundle core inside esphome
data["esphome"]["core"] = data.pop("core")["core"]
for c, s in data.items():
write_file(c, s)
def setEnum(obj, items):
obj[S_TYPE] = "enum"
obj["values"] = items
def isConvertibleSchema(schema):
if schema is None:
return False
if isinstance(schema, (cv.Schema, cv.All)):
return True
if repr(schema) in ejs.hidden_schemas:
return True
if repr(schema) in ejs.typed_schemas:
return True
if repr(schema) in ejs.list_schemas:
return True
if repr(schema) in ejs.registry_schemas:
return True
if isinstance(schema, dict):
for k in schema.keys():
if isinstance(k, (cv.Required, cv.Optional)):
return True
return False
def convert_config(schema, path):
converted = {}
convert_1(schema, converted, path)
return converted
def convert_1(schema, config_var, path):
"""config_var can be a config_var or a schema: both are dicts
config_var has a S_TYPE property, if this is S_SCHEMA, then it has a S_SCHEMA property
schema does not have a type property, schema can have optionally both S_CONFIG_VARS and S_EXTENDS
"""
repr_schema = repr(schema)
if repr_schema in known_schemas:
schema_info = known_schemas[(repr_schema)]
for (schema_instance, name) in schema_info:
if schema_instance is schema:
assert S_CONFIG_VARS not in config_var
assert S_EXTENDS not in config_var
if not S_TYPE in config_var:
config_var[S_TYPE] = S_SCHEMA
assert config_var[S_TYPE] == S_SCHEMA
if S_SCHEMA not in config_var:
config_var[S_SCHEMA] = {}
if S_EXTENDS not in config_var[S_SCHEMA]:
config_var[S_SCHEMA][S_EXTENDS] = [name]
else:
config_var[S_SCHEMA][S_EXTENDS].append(name)
return
# Extended schemas are tracked when the .extend() is used in a schema
if repr_schema in ejs.extended_schemas:
extended = ejs.extended_schemas.get(repr_schema)
# The midea actions are extending an empty schema (resulted in the templatize not templatizing anything)
# this causes a recursion in that this extended looks the same in extended schema as the extended[1]
if repr_schema == repr(extended[1]):
assert path.startswith("midea_ac/")
return
assert len(extended) == 2
convert_1(extended[0], config_var, path + "/extL")
convert_1(extended[1], config_var, path + "/extR")
return
if isinstance(schema, cv.All):
i = 0
for inner in schema.validators:
i = i + 1
convert_1(inner, config_var, path + f"/val {i}")
return
if hasattr(schema, "validators"):
i = 0
for inner in schema.validators:
i = i + 1
convert_1(inner, config_var, path + f"/val {i}")
if isinstance(schema, cv.Schema):
convert_1(schema.schema, config_var, path + "/all")
return
if isinstance(schema, dict):
convert_keys(config_var, schema, path)
return
if repr_schema in ejs.list_schemas:
config_var["is_list"] = True
items_schema = ejs.list_schemas[repr_schema][0]
convert_1(items_schema, config_var, path + "/list")
return
if DUMP_RAW:
config_var["raw"] = repr_schema
# pylint: disable=comparison-with-callable
if schema == cv.boolean:
config_var[S_TYPE] = "boolean"
elif schema == automation.validate_potentially_and_condition:
config_var[S_TYPE] = "registry"
config_var["registry"] = "condition"
elif schema == cv.int_ or schema == cv.int_range:
config_var[S_TYPE] = "integer"
elif schema == cv.string or schema == cv.string_strict or schema == cv.valid_name:
config_var[S_TYPE] = "string"
elif isinstance(schema, vol.Schema):
# test: esphome/project
config_var[S_TYPE] = "schema"
config_var["schema"] = convert_config(schema.schema, path + "/s")["schema"]
elif repr_schema in pin_validators:
config_var |= pin_validators[repr_schema]
config_var[S_TYPE] = "pin"
elif repr_schema in ejs.hidden_schemas:
schema_type = ejs.hidden_schemas[repr_schema]
data = schema(ejs.jschema_extractor)
# enums, e.g. esp32/variant
if schema_type == "one_of":
config_var[S_TYPE] = "enum"
config_var["values"] = list(data)
elif schema_type == "enum":
config_var[S_TYPE] = "enum"
config_var["values"] = list(data.keys())
elif schema_type == "maybe":
config_var[S_TYPE] = "maybe"
config_var["schema"] = convert_config(data, path + "/maybe")["schema"]
# esphome/on_boot
elif schema_type == "automation":
extra_schema = None
config_var[S_TYPE] = "trigger"
if automation.AUTOMATION_SCHEMA == ejs.extended_schemas[repr(data)][0]:
extra_schema = ejs.extended_schemas[repr(data)][1]
if (
extra_schema is not None and len(extra_schema) > 1
): # usually only trigger_id here
config = convert_config(extra_schema, path + "/extra")
if "schema" in config:
automation_schema = config["schema"]
if not (
len(automation_schema["config_vars"]) == 1
and "trigger_id" in automation_schema["config_vars"]
):
automation_schema["config_vars"]["then"] = {S_TYPE: "trigger"}
if "trigger_id" in automation_schema["config_vars"]:
automation_schema["config_vars"].pop("trigger_id")
config_var[S_TYPE] = "trigger"
config_var["schema"] = automation_schema
# some triggers can have a list of actions directly, while others needs to have some other configuration,
# e.g. sensor.on_value_rang, and the list of actions is only accepted under "then" property.
try:
schema({"delay": "1s"})
except cv.Invalid:
config_var["has_required_var"] = True
else:
print("figure out " + path)
elif schema_type == "effects":
config_var[S_TYPE] = "registry"
config_var["registry"] = "light.effects"
config_var["filter"] = data[0]
elif schema_type == "templatable":
config_var["templatable"] = True
convert_1(data, config_var, path + "/templat")
elif schema_type == "triggers":
# remote base
convert_1(data, config_var, path + "/trigger")
elif schema_type == "sensor":
schema = data
convert_1(data, config_var, path + "/trigger")
else:
raise Exception("Unknown extracted schema type")
elif repr_schema in ejs.registry_schemas:
solve_registry.append((ejs.registry_schemas[repr_schema], config_var))
elif repr_schema in ejs.typed_schemas:
config_var[S_TYPE] = "typed"
types = config_var["types"] = {}
typed_schema = ejs.typed_schemas[repr_schema]
if len(typed_schema) > 1:
config_var["typed_key"] = typed_schema[1].get("key", CONF_TYPE)
for schema_key, schema_type in typed_schema[0][0].items():
config = convert_config(schema_type, path + "/type_" + schema_key)
types[schema_key] = config["schema"]
elif DUMP_UNKNOWN:
if S_TYPE not in config_var:
config_var["unknown"] = repr_schema
if DUMP_PATH:
config_var["path"] = path
def get_overridden_config(key, converted):
# check if the key is in any extended schema in this converted schema, i.e.
# if we see a on_value_range in a dallas sensor, then this is overridden because
# it is already defined in sensor
assert S_CONFIG_VARS not in converted and S_EXTENDS not in converted
config = converted.get(S_SCHEMA, {})
return get_overridden_key_inner(key, config, {})
def get_overridden_key_inner(key, config, ret):
if S_EXTENDS not in config:
return ret
for s in config[S_EXTENDS]:
p = s.partition(".")
s1 = output.get(p[0], {}).get(S_SCHEMAS, {}).get(p[2], {}).get(S_SCHEMA)
if s1:
if key in s1.get(S_CONFIG_VARS, {}):
for k, v in s1.get(S_CONFIG_VARS)[key].items():
if k not in ret: # keep most overridden
ret[k] = v
get_overridden_key_inner(key, s1, ret)
return ret
def convert_keys(converted, schema, path):
for k, v in schema.items():
# deprecated stuff
if repr(v).startswith("<function invalid"):
continue
result = {}
if isinstance(k, cv.GenerateID):
result["key"] = "GeneratedID"
elif isinstance(k, cv.Required):
result["key"] = "Required"
elif (
isinstance(k, cv.Optional)
or isinstance(k, cv.Inclusive)
or isinstance(k, cv.Exclusive)
):
result["key"] = "Optional"
else:
converted["key"] = "String"
converted["key_dump"] = str(k)
esphome_core.CORE.data = {
esphome_core.KEY_CORE: {esphome_core.KEY_TARGET_PLATFORM: "esp8266"}
}
if hasattr(k, "default") and str(k.default) != "...":
default_value = k.default()
if default_value is not None:
result["default"] = str(default_value)
# Do value
convert_1(v, result, path + f"/{str(k)}")
if "schema" not in converted:
converted[S_TYPE] = "schema"
converted["schema"] = {S_CONFIG_VARS: {}}
if S_CONFIG_VARS not in converted["schema"]:
converted["schema"][S_CONFIG_VARS] = {}
for base_k, base_v in get_overridden_config(k, converted).items():
if base_k in result and base_v == result[base_k]:
result.pop(base_k)
converted["schema"][S_CONFIG_VARS][str(k)] = result
build_schema()

View File

@@ -247,9 +247,7 @@ logger:
web_server:
port: 8080
ota: true
css_url: https://esphome.io/_static/webserver-v1.min.css
js_url: https://esphome.io/_static/webserver-v1.min.js
version: 2
power_supply:
id: "atx_power_supply"
@@ -800,6 +798,17 @@ sensor:
value: 12345
total:
name: "Pulse Meter Total"
- platform: qmp6988
temperature:
name: "Living Temperature QMP"
oversampling: 32x
pressure:
name: "Living Pressure QMP"
oversampling: 2x
address: 0x70
update_interval: 30s
iir_filter: 16x
i2c_id: i2c_bus
- platform: rotary_encoder
name: "Rotary Encoder"
id: rotary_encoder1
@@ -2209,6 +2218,17 @@ display:
- platform: lcd_pcf8574
dimensions: 18x4
address: 0x3F
user_characters:
- position: 0
data:
- 0b00000
- 0b01010
- 0b00000
- 0b00100
- 0b00100
- 0b10001
- 0b01110
- 0b00000
lambda: |-
it.print("Hello World!");
i2c_id: i2c_bus
@@ -2596,6 +2616,16 @@ text_sensor:
canbus_id: esp32_internal_can
can_id: 23
data: [0x10, 0x20, 0x30]
- canbus.send:
canbus_id: mcp2515_can
can_id: 24
remote_transmission_request: true
data: []
- canbus.send:
canbus_id: esp32_internal_can
can_id: 24
remote_transmission_request: true
data: []
- platform: template
name: Template Text Sensor
id: ${textname}_text

View File

@@ -263,6 +263,10 @@ sensor:
name: 'Inkbird IBS-TH1 Humidity'
battery_level:
name: 'Inkbird IBS-TH1 Battery Level'
- platform: xiaomi_rtcgq02lm
id: motion_rtcgq02lm
battery_level:
name: 'Mi Motion Sensor 2 Battery level'
- platform: ltr390
uv:
name: "LTR390 UV"
@@ -417,6 +421,14 @@ binary_sensor:
name: 'CGPR1 Idle Time'
illuminance:
name: 'CGPR1 Illuminance'
- platform: xiaomi_rtcgq02lm
id: motion_rtcgq02lm
motion:
name: 'Mi Motion Sensor 2'
light:
name: 'Mi Motion Sensor 2 Light'
button:
name: 'Mi Motion Sensor 2 Button'
esp32_ble_tracker:
on_ble_advertise:
@@ -457,6 +469,11 @@ xiaomi_ble:
mopeka_ble:
xiaomi_rtcgq02lm:
- id: motion_rtcgq02lm
mac_address: 01:02:03:04:05:06
bindkey: '48403ebe2d385db8d0c187f81e62cb64'
#esp32_ble_beacon:
# type: iBeacon
# uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98'

View File

@@ -349,6 +349,24 @@ sensor:
name: 'Temperature'
humidity:
name: 'Humidity'
- platform: hydreon_rgxx
model: "RG 9"
uart_id: uart6
id: "hydreon_rg9"
moisture:
name: "hydreon_rain"
id: hydreon_rain
- platform: hydreon_rgxx
model: "RG_15"
uart_id: uart6
acc:
name: "hydreon_acc"
event_acc:
name: "hydreon_event_acc"
total_acc:
name: "hydreon_total_acc"
r_int:
name: "hydreon_r_int"
- platform: adc
pin: VCC
id: my_sensor
@@ -795,6 +813,11 @@ binary_sensor:
on_press:
then:
- cover.toggle: time_based_cover
- cover.toggle: endstop_cover
- platform: hydreon_rgxx
hydreon_rgxx_id: "hydreon_rg9"
too_cold:
name: "rg9_toocold"
- platform: template
id: 'pzemac_reset_energy'
on_press:
@@ -1060,6 +1083,7 @@ climate:
cover:
- platform: endstop
name: Endstop Cover
id: endstop_cover
stop_action:
- switch.turn_on: gpio_switch1
open_endstop: my_binary_sensor
@@ -1205,6 +1229,12 @@ light:
name: Icicle Lights
pin_a: out
pin_b: out2
- platform: sonoff_d1
uart_id: uart2
use_rm433_remote: False
name: Sonoff D1 Dimmer
id: d1_light
restore_mode: RESTORE_DEFAULT_OFF
servo:
id: my_servo

View File

@@ -49,6 +49,19 @@ modbus_controller:
address: 0x2
modbus_id: mod_bus1
mqtt:
broker: test.mosquitto.org
port: 1883
discovery: true
discovery_prefix: homeassistant
idf_send_async: false
on_message:
topic: testing/sensor/testing_sensor/state
qos: 0
then:
- lambda: |-
ESP_LOGD("Mqtt Test","testing/sensor/testing_sensor/state=[%s]",x.c_str());
binary_sensor:
- platform: gpio
pin: GPIO0