Compare commits

..

299 Commits

Author SHA1 Message Date
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
ee58ad1ac0 Bump esphome-dashboard to 20220113.1 (#3038) 2022-01-13 10:52:57 +13:00
Jesse Hills
c0ff899812 Generate basic config for esphome-web devices (#3036) 2022-01-12 19:37:56 +13:00
Oxan van Leeuwen
d9c938de33 Introduce big- and little-endian integer types (#2997) 2022-01-12 16:50:03 +13:00
Martin
56547b3d50 [Modbus_controller] Fix duplicate cmd check (#3031) 2022-01-12 16:38:13 +13:00
Sympatron GmbH
5026bc7a78 Native ESP32 CAN support (#1629)
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-12 08:54:35 +13:00
Andreas Soehlke
27364ee72c Add cd74hc4067 multiplexer (#2431)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: asoehlke <git@soehlke.de>
2022-01-11 16:59:57 +13:00
Jesse Hills
ece71a0228 Run post scripts for factory binaries for flashing (#3003)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-01-11 15:24:26 +13:00
Oxan van Leeuwen
073828235f Deprecate virtual methods to set entity properties (#3021) 2022-01-10 13:32:39 +01:00
Stefan Grufman
41bcc8c0f4 Nexa 433MHz RF protocol (#2037)
Co-authored-by: Stefan Grufman <stefan.grufman@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-10 23:35:39 +13:00
Chris Nussbaum
a0ea2aae6e Add an action for pzemac to reset the total energy (#2480) 2022-01-10 23:13:39 +13:00
Jeffrey Borg
f34b46a621 Fix heatpumpir codegen min/max temperatures (#3025) 2022-01-10 16:48:05 +13:00
Lubos Horacek
7217a4f7a4 Fix display picture for nextion display (#3018) 2022-01-10 14:08:38 +13:00
Oxan van Leeuwen
6383eca54a Clean-up random helper functions (#3022) 2022-01-10 13:50:26 +13:00
Martin
e55bd1e559 [Modbus_controller] Fix binary sensor lambda (#3020) 2022-01-10 12:29:29 +13:00
MiKuBB
9e8b701dea Adding sdm_meter ability to report total power (#2959) 2022-01-10 12:23:01 +13:00
rsumner
a4431abea8 MCP3204 4-channel 12-bit ADC component (#2895)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-10 12:04:48 +13:00
Kamil Trzciński
5844c1767b Extend esp32_camera with requester to improve performance (#2813) 2022-01-10 11:58:49 +13:00
Sergey Dudanov
9a70bfa471 New Midea IR component, improvements and fixes (#2847)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-10 11:47:19 +13:00
Valentin Ochs
b406c6403c Create new kalman_combinator component (#2965) 2022-01-09 23:44:36 +01:00
Oxan van Leeuwen
499625f266 Convert is_callable to a backport of std::is_invocable (#3023) 2022-01-10 11:07:37 +13:00
Martin
6b773553fc Add turn_on/off trigger to slow_pwm (#2921)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-09 19:49:57 +01:00
Joshua Spence
15fe049a99 Add restore_mode to output switch (#3016)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-01-09 19:47:00 +01:00
stegm
e4555f6997 Fix register ranges in modbus controller (#2981) 2022-01-09 16:24:23 +01:00
Otto Winter
470071e0b0 Bump docker dependencies (#3019) 2022-01-08 14:15:05 +01:00
Jesse Hills
ea1be8e7bf Add MCP47A1 DAC output (#3014) 2022-01-08 21:35:55 +13:00
stegm
84a830195f Fix offset bug in modbus text sensor. (#3006) 2022-01-06 16:40:22 +01:00
Oxan van Leeuwen
e62c3e00c1 Bump PlatformIO to 5.2.4 and zeroconf to 0.37.0 (#3007) 2022-01-06 16:36:23 +01:00
Oxan van Leeuwen
07e790f900 Drop uint{32,64}_to_string() helper functions (#3009) 2022-01-06 16:36:11 +01:00
Oxan van Leeuwen
640142fc0c Introduce str_lower_case() and str_upper_case() helpers (#3008) 2022-01-06 16:35:59 +01:00
Oxan van Leeuwen
5c339d4597 Convert clamp() helper to backport of std::clamp() (#3010) 2022-01-07 00:56:10 +13:00
Oxan van Leeuwen
a4931f5d78 Clean-up reverse_bits helpers (#3011) 2022-01-07 00:54:58 +13:00
Martin
5e1e543b06 Add support for BMP388 / BMP 390 pressure and temperature sensor (#2716) 2022-01-06 15:01:50 +13:00
Pavel Skuratovich
df929f9445 Fix SlowPWM output switch at the end of period (#2984) 2022-01-05 21:31:11 +01:00
Oxan van Leeuwen
d8e719d1c4 Support clang-tidy for ESP32 variants (#3001) 2022-01-05 21:30:15 +01:00
mknjc
3067e482fc atc mithermometer: Add possibility to report signal strength (#3000) 2022-01-05 16:43:37 +13:00
Martin
ed5930e934 SGP40 - Reduce delay in measurement (#2996) 2022-01-05 10:05:19 +13:00
Oxan van Leeuwen
ffea3597f4 Set correct include_dir in platformio.ini (#2999) 2022-01-04 21:59:34 +01:00
Oxan van Leeuwen
193d3e0206 Fix clang-tidy with multiple ESP32 toolchains installed (#2998) 2022-01-05 08:34:17 +13:00
Gonzalo Paniagua Javier
c8f4fbb7dd Honor user set values for col/row start for INITR_MINI_160X80. (#2976)
If the caller sets a value for colstart and/or rowstart when using the INITR_MINI_160X80 model, use those values instead of the default 24 and 0.

After this patch devices with a 160x80 TFT like the m5stick C can set row/col start (26, 1 for m5stick) and avoid garbage lines showing in the display.
2022-01-04 11:02:53 +01:00
Snōwball
c855bc31b4 Add bl0940 component used by e.g. tuya devices (#1904)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-01-04 10:38:58 +01:00
Martin
b924b179ab Modbus: add binary output (#2931)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-01-04 10:19:18 +01:00
Jesse Hills
3df0fee3de Dont validate baud_rate for sim800l platform (#2945) 2022-01-04 10:16:40 +01:00
Jesse Hills
b601560e81 Apply --no-use-pep517 for docker images (#2985) 2022-01-04 10:16:02 +01:00
Oxan van Leeuwen
e5775cf812 Introduce bit_cast() backport (#2991) 2022-01-04 10:14:57 +01:00
Igor Scheller
26dd1f8532 Set UTF-8 encoding and version for prometheus /metrics (#2993) 2022-01-04 10:14:38 +01:00
Oxan van Leeuwen
5143a5b5c5 Use to_string() from STL when available (#2992) 2022-01-03 23:30:03 +01:00
Stefan Agner
15ce27992e Support ISR based pulse counter on ESP32-C3 (#2983) 2022-01-04 11:06:43 +13:00
Oxan van Leeuwen
dbc2812022 Improve PSRAM support (#2884) 2022-01-04 10:35:15 +13:00
Martin
dce3713f12 Fix HTTPRequestComponent::get_string return value (#2987)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-01-03 19:40:05 +01:00
Christopher Masto
f849d45bb6 Add logging for some Nextion errors that didn't have any (#2957) 2022-01-03 19:09:25 +01:00
arunderwood
8ad06fb9ea Add SH1107_128x64 to the ssd1306 component (#2967) 2022-01-03 19:08:16 +01:00
David Buezas
9124d9d6e6 Change unset ESPHOME_LOG_LEVEL fallback to NONE (#2982)
Co-authored-by: David Buezas <david.buezas@klarna.com>
2022-01-03 18:58:35 +01:00
Martin
45ebe51e4f Modbus: fix response parsing error for coil write (#2986) 2022-01-03 18:28:28 +01:00
Martin
407661d56b Fix compile error for idf projects with ArduinoJson 6 (#2979)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-01-03 18:19:01 +01:00
Paulus Schoutsen
998d4229af Use template path (#2961) 2022-01-03 08:57:09 -08:00
Stefan Agner
a02d2e2e11 Explicitly use overloaded begin() for I2C master initialization (#2978)
Arduino 2.0.1 and newer support slave and master mode. The two modes
have a begin() method with different signature:

```
// Slave Begin
bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency)

// Master Begin
bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
```

Use type casting to make sure that overloaded method for master mode
is used.
2022-01-03 16:37:21 +01:00
Stefan Agner
72fa68849f Don't use pyproject.toml for esphome build (#2980) 2022-01-03 22:11:28 +13:00
Jesse Hills
33f17f75a0 Upgrade ArduinoJson to 6.18.5 and migrate code (#2844) 2022-01-01 22:31:43 +13:00
MrEditor97
23edb18d7e INA260 Current and Power Sensor support (#2788) 2021-12-31 22:08:49 +13:00
arunderwood
07ff3a853f Add pin aliases for featheresp32-s2 (#2970) 2021-12-31 20:11:28 +13:00
Sebastian Raff
2cf36bdb46 Fix switch log state if inverted (#2960) 2021-12-30 16:05:31 +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
f859b346a6 Remove -e for hassio images (#2964) 2021-12-30 10:42:22 +13:00
marsjan155
cb0677cafe ST7920 ESP32 fix (#2962)
Co-authored-by: Marcin Depa <m.depa91@gmail.com>
2021-12-30 10:34:30 +13:00
Daniel Hyles
c6956527d1 Remove Content-Length header from camera snapshot response (#2860)
* Update camera_web_server.cpp

Removed the duplicated CONTENT_LENGTH header

* Update camera_web_server.cpp

* Update camera_web_server.cpp
2021-12-28 09:32:17 +13:00
Jesse Hills
72c6bfaa50 Revert "Disable nightly dev build" (#2944) 2021-12-23 09:38:43 +13:00
Jesse Hills
7927b5f624 Workaround installing as editable package not working (#2936) 2021-12-23 08:43:17 +13:00
Jesse Hills
b7aad39daf Only allow internal pins for dht sensor (#2940) 2021-12-23 08:31:56 +13:00
Jesse Hills
f48de6dd43 Disable nightly dev build (#2943) 2021-12-22 23:02:58 +13:00
Jesse Hills
79d73d8f8b Add option to load docker image when building (#2938) 2021-12-22 20:49:04 +13:00
Jesse Hills
cc5947467f Require arduino in webserver for better validation (#2941) 2021-12-22 20:08:54 +13:00
George
e152f128c8 Change HDC1080 init instruction failure from error to warning (#2927)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-22 15:35:01 +13:00
Jesse Hills
99bd808ebe Update curl package version in docker (#2939) 2021-12-22 15:27:34 +13:00
Jan Čermák
beb5f3dc9d bang_bang: respect set cool- and heat-only modes (#2926) 2021-12-22 15:27:16 +13:00
Jesse Hills
f5c3b3446f Support inkplate10 (#2937) 2021-12-22 12:56:52 +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
jsuanet
f431c7402f Add shutdown and safe_mode button (#2918)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Jos Suanet <jos@suanet.net>
2021-12-20 22:25:36 +01: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
Oxan van Leeuwen
4907e6f6d7 Fix MQTT button press action (#2917) 2021-12-21 08:19:20 +13:00
Jonas De Kegel
1ccee86705 Fix tm1637 bootloop (#2929) 2021-12-20 18:06:04 +01:00
Jonas De Kegel
542fb2175b Support inverted tm1637 display (#2878)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-20 09:30:35 +01:00
Frank Langtind
6ec9cfb044 Add Tuya Number support (#2765) 2021-12-20 14:35:10 +13:00
Benny de Leeuw
66e0ff8392 Add growatt modbus sensor (#2922)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-20 14:30:23 +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
213 changed files with 5715 additions and 1243 deletions

View File

@@ -51,26 +51,26 @@ jobs:
name: Run script/clang-format
- id: clang-tidy
name: Run script/clang-tidy for ESP8266
options: --environment esp8266-tidy --grep USE_ESP8266
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
pio_cache_key: tidyesp8266
- id: clang-tidy
name: Run script/clang-tidy for ESP32 1/4
options: --environment esp32-tidy --split-num 4 --split-at 1
name: Run script/clang-tidy for ESP32 Arduino 1/4
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 2/4
options: --environment esp32-tidy --split-num 4 --split-at 2
name: Run script/clang-tidy for ESP32 Arduino 2/4
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 3/4
options: --environment esp32-tidy --split-num 4 --split-at 3
name: Run script/clang-tidy for ESP32 Arduino 3/4
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 4/4
options: --environment esp32-tidy --split-num 4 --split-at 4
name: Run script/clang-tidy for ESP32 Arduino 4/4
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 esp-idf
name: Run script/clang-tidy for ESP32 IDF
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf

View File

@@ -28,13 +28,16 @@ esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/bl0940/* @tobias-
esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmp3xx/* @martgras
esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz
@@ -55,6 +58,7 @@ esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter
@@ -65,6 +69,7 @@ esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/growatt_solar/* @leeuwte
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
@@ -74,11 +79,13 @@ esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv_serial/* @esphome/core
esphome/components/ina260/* @MrEditor97
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter
esphome/components/kalman_combinator/* @Cat-Ion
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core
@@ -92,10 +99,13 @@ esphome/components/mcp23x08_base/* @jesserockz
esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner
esphome/components/mcp47a1/* @jesserockz
esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
@@ -123,6 +133,7 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet
@@ -132,7 +143,7 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @paulmonigatti
esphome/components/safe_mode/* @jsuanet @paulmonigatti
esphome/components/scd4x/* @sjtrny
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
@@ -142,7 +153,7 @@ esphome/components/select/* @esphome/core
esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny
esphome/components/shutdown/* @esphome/core
esphome/components/shutdown/* @esphome/core @jsuanet
esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/socket/* @esphome/core
@@ -179,6 +190,7 @@ esphome/components/toshiba/* @kbx81
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1
esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra

View File

@@ -5,12 +5,12 @@
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
FROM debian:bullseye-20211011-slim AS base-docker-amd64
FROM debian:bullseye-20211011-slim AS base-docker-arm64
FROM debian:bullseye-20211011-slim AS base-docker-armv7
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 debian:bullseye-20211220-slim AS base-docker-amd64
FROM debian:bullseye-20211220-slim AS base-docker-arm64
FROM debian:bullseye-20211220-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
@@ -27,7 +27,7 @@ RUN \
python3-cryptography=3.3.2-1 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \
curl=7.74.0-1.3+b1 \
curl=7.74.0-1.3+deb11u1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
@@ -42,8 +42,8 @@ ENV \
RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.36.2 \
platformio==5.2.2 \
wheel==0.37.1 \
platformio==5.2.4 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \
@@ -64,7 +64,7 @@ RUN \
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
@@ -112,7 +112,7 @@ RUN \
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
# Labels
LABEL \

View File

@@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
build_parser = subparsers.add_parser("build", help="Build the image")
build_parser.add_argument("--push", help="Also push the images", action="store_true")
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
@@ -132,6 +133,8 @@ def main():
cmd += ["--tag", img]
if args.push:
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
if args.load:
cmd += ["--load"]
run_command(*cmd, ".")
elif args.command == "manifest":

View File

@@ -75,8 +75,7 @@ from esphome.cpp_types import ( # noqa
optional,
arduino_json_ns,
JsonObject,
JsonObjectRef,
JsonObjectConstRef,
JsonObjectConst,
Controller,
GPIOPin,
InternalGPIOPin,

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

@@ -20,6 +20,7 @@ namespace esphome {
namespace api {
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) {
@@ -704,7 +705,9 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
return;
if (this->image_reader_.available())
return;
this->image_reader_.set_image(std::move(image));
if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
image->was_requested_by(esphome::esp32_camera::IDLE))
this->image_reader_.set_image(std::move(image));
}
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg;
@@ -722,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
return;
if (msg.single)
esp32_camera::global_esp32_camera->request_image();
if (msg.stream)
esp32_camera::global_esp32_camera->request_stream();
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
if (msg.stream) {
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
});
}
}
#endif

View File

@@ -721,7 +721,7 @@ APIError APINoiseFrameHelper::shutdown(int how) {
}
extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); }
void noise_rand_bytes(void *output, size_t len) { esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len); }
}
#endif // USE_API_NOISE

View File

@@ -12,10 +12,10 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};

View File

@@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
this->battery_voltage_->publish_state(*res->battery_voltage);
success = true;
}
if (this->signal_strength_ != nullptr)
this->signal_strength_->publish_state(device.get_rssi());
return success;
}

View File

@@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; }
protected:
uint64_t address_;
@@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);

View File

@@ -6,15 +6,18 @@ from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_MAC_ADDRESS,
CONF_HUMIDITY,
CONF_SIGNAL_STRENGTH,
CONF_TEMPERATURE,
CONF_ID,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_DECIBEL_MILLIWATT,
UNIT_PERCENT,
UNIT_VOLT,
)
@@ -59,6 +62,13 @@ CONFIG_SCHEMA = (
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@@ -85,3 +95,6 @@ async def to_code(config):
if CONF_BATTERY_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config:
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH])
cg.add(var.set_signal_strength(sens))

View File

@@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() {
climate::ClimateAction target_action;
if (too_cold) {
// too cold -> enable heating if possible, else idle
if (this->supports_heat_)
// too cold -> enable heating if possible and enabled, else idle
if (this->supports_heat_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT))
target_action = climate::CLIMATE_ACTION_HEATING;
else
target_action = climate::CLIMATE_ACTION_IDLE;
} else if (too_hot) {
// too hot -> enable cooling if possible, else idle
if (this->supports_cool_)
// too hot -> enable cooling if possible and enabled, else idle
if (this->supports_cool_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL))
target_action = climate::CLIMATE_ACTION_COOLING;
else
target_action = climate::CLIMATE_ACTION_IDLE;
} else {
// neither too hot nor too cold -> in range
if (this->supports_cool_ && this->supports_heat_) {
// if supports both ends, go to idle action
if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
// if supports both ends and both cooling and heating enabled, go to idle action
target_action = climate::CLIMATE_ACTION_IDLE;
} else {
// else use current mode and don't change (hysteresis)

View File

@@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev
std::string BinarySensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->device_class();
#pragma GCC diagnostic pop
}
void BinarySensor::add_filter(Filter *filter) {
filter->parent_ = this;

View File

@@ -74,7 +74,10 @@ class BinarySensor : public EntityBase {
// ========== OVERRIDE METHODS ==========
// (You'll only need this when creating your own custom binary sensor)
/// Get the default device class for this sensor, or empty string for no default.
/** Override this to set the default device class.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual std::string device_class();
protected:

View File

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

View File

@@ -0,0 +1,137 @@
#include "bl0940.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940";
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
static const uint8_t BL0940_FULL_PACKET = 0xAA;
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0940_REG_MODE = 0x18;
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
const uint8_t BL0940_INIT[5][6] = {
// Reset to default
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
void BL0940::loop() {
DataPacket buffer;
if (!this->available()) {
return;
}
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) {
received_package_(&buffer);
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
}
}
bool BL0940::validate_checksum(const DataPacket *data) {
uint8_t checksum = BL0940_READ_COMMAND;
// Whole package but checksum
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
checksum += data->raw[i];
}
checksum ^= 0xFF;
if (checksum != data->checksum) {
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
}
return checksum == data->checksum;
}
void BL0940::update() {
this->flush();
this->write_byte(BL0940_READ_COMMAND);
this->write_byte(BL0940_FULL_PACKET);
}
void BL0940::setup() {
for (auto i : BL0940_INIT) {
this->write_array(i, 6);
delay(1);
}
this->flush();
}
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
auto tb = (float) (temperature.h << 8 | temperature.l);
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
sensor->get_name().c_str(), sensor->get_state(), converted_temp);
return 0.0f;
}
sensor->publish_state(converted_temp);
}
return converted_temp;
}
void BL0940::received_package_(const DataPacket *data) const {
// Bad header
if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
float watt = (float) to_int32_t(data->watt) / power_reference_;
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
float total_energy_consumption = (float) cf_cnt / energy_reference_;
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(v_rms);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(i_rms);
}
if (power_sensor_ != nullptr) {
power_sensor_->publish_state(watt);
}
if (energy_sensor_ != nullptr) {
energy_sensor_->publish_state(total_energy_consumption);
}
ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
total_energy_consumption, tps1, tps2);
}
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0940:");
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
LOG_SENSOR("", "Energy", this->energy_sensor_);
LOG_SENSOR("", "Internal temperature", this->internal_temperature_sensor_);
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
}
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,109 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace bl0940 {
static const float BL0940_PREF = 1430;
static const float BL0940_UREF = 33000;
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
// Measured to 297J per click according to power consumption of 5 minutes
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
static const float BL0940_EREF = 3.6e6 / 297;
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
uint8_t h;
} __attribute__((packed));
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t h;
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
int8_t h;
} __attribute__((packed));
// Caveat: All these values are big endian (low - middle - high)
union DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t raw[35];
struct {
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
ube24_t i_fast_rms; // 0x00
ube24_t i_rms; // 0x04
ube24_t RESERVED0; // reserved
ube24_t v_rms; // 0x06
ube24_t RESERVED1; // reserved
sbe24_t watt; // 0x08
ube24_t RESERVED2; // reserved
ube24_t cf_cnt; // 0x0A
ube24_t RESERVED3; // reserved
ube16_t tps1; // 0x0c
uint8_t RESERVED4; // value of 0x00
ube16_t tps2; // 0x0c
uint8_t RESERVED5; // value of 0x00
uint8_t checksum; // checksum
};
} __attribute__((packed));
class BL0940 : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
internal_temperature_sensor_ = internal_temperature_sensor;
}
void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) {
external_temperature_sensor_ = external_temperature_sensor;
}
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
// NB This may be negative as the circuits is seemingly able to measure
// power in both directions
sensor::Sensor *power_sensor_;
sensor::Sensor *energy_sensor_;
sensor::Sensor *internal_temperature_sensor_;
sensor::Sensor *external_temperature_sensor_;
// Max difference between two measurements of the temperature. Used to avoid noise.
float max_temperature_diff_{0};
// Divide by this to turn into Watt
float power_reference_ = BL0940_PREF;
// Divide by this to turn into Volt
float voltage_reference_ = BL0940_UREF;
// Divide by this to turn into Ampere
float current_reference_ = BL0940_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0940_EREF;
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
static uint32_t to_uint32_t(ube24_t input);
static int32_t to_int32_t(sbe24_t input);
static bool validate_checksum(const DataPacket *data);
void received_package_(const DataPacket *data) const;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,106 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
DEPENDENCIES = ["uart"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0940),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE,
ICON_EMPTY,
2,
DEVICE_CLASS_CURRENT,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
UNIT_KILOWATT_HOURS,
ICON_EMPTY,
0,
DEVICE_CLASS_ENERGY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_EMPTY,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_NONE,
),
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_EMPTY,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_NONE,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
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)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config:
conf = config[CONF_INTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config:
conf = config[CONF_EXTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_external_temperature_sensor(sens))

View File

View File

@@ -0,0 +1,388 @@
/*
based on BMP388_DEV by Martin Lindupp
under MIT License (MIT)
Copyright (C) Martin Lindupp 2020
http://github.com/MartinL1/BMP388_DEV
*/
#include "bmp3xx.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace bmp3xx {
static const char *const TAG = "bmp3xx.sensor";
static const LogString *chip_type_to_str(uint8_t chip_type) {
switch (chip_type) {
case BMP388_ID:
return LOG_STR("BMP 388");
case BMP390_ID:
return LOG_STR("BMP 390");
default:
return LOG_STR("Unknown Chip Type");
}
}
static const LogString *oversampling_to_str(Oversampling oversampling) {
switch (oversampling) {
case Oversampling::OVERSAMPLING_NONE:
return LOG_STR("None");
case Oversampling::OVERSAMPLING_X2:
return LOG_STR("2x");
case Oversampling::OVERSAMPLING_X4:
return LOG_STR("4x");
case Oversampling::OVERSAMPLING_X8:
return LOG_STR("8x");
case Oversampling::OVERSAMPLING_X16:
return LOG_STR("16x");
case Oversampling::OVERSAMPLING_X32:
return LOG_STR("32x");
default:
return LOG_STR("");
}
}
static const LogString *iir_filter_to_str(IIRFilter filter) {
switch (filter) {
case IIRFilter::IIR_FILTER_OFF:
return LOG_STR("OFF");
case IIRFilter::IIR_FILTER_2:
return LOG_STR("2x");
case IIRFilter::IIR_FILTER_4:
return LOG_STR("4x");
case IIRFilter::IIR_FILTER_8:
return LOG_STR("8x");
case IIRFilter::IIR_FILTER_16:
return LOG_STR("16x");
case IIRFilter::IIR_FILTER_32:
return LOG_STR("32x");
case IIRFilter::IIR_FILTER_64:
return LOG_STR("64x");
case IIRFilter::IIR_FILTER_128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
void BMP3XXComponent::setup() {
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP3XX...");
// Call the Device base class "initialise" function
if (!reset()) {
ESP_LOGE(TAG, "Failed to reset BMP3XX...");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
}
if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) {
ESP_LOGE(TAG, "Can't read chip id");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) {
ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
return;
}
// set sensor in sleep mode
stop_conversion();
// Read the calibration parameters into the params structure
if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) {
ESP_LOGE(TAG, "Can't read calibration data");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
compensation_float_params_.param_T1 =
(float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters
compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f);
compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f);
compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f);
compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f);
compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f);
compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f);
compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f);
compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f);
compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f);
compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f);
compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f);
compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f);
compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f);
// Initialise the BMP388 IIR filter register
if (!set_iir_filter(this->iir_filter_)) {
ESP_LOGE(TAG, "Failed to set IIR filter");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// Set power control registers
pwr_ctrl_.bit.press_en = 1;
pwr_ctrl_.bit.temp_en = 1;
// Disable pressure if no sensor defined
// keep temperature enabled since it's needed for compensation
if (this->pressure_sensor_ == nullptr) {
pwr_ctrl_.bit.press_en = 0;
this->pressure_oversampling_ = OVERSAMPLING_NONE;
}
// just disable oeversampling for temp if not used
if (this->temperature_sensor_ == nullptr) {
this->temperature_oversampling_ = OVERSAMPLING_NONE;
}
// Initialise the BMP388 oversampling register
if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) {
ESP_LOGE(TAG, "Failed to set oversampling register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
}
void BMP3XXComponent::dump_config() {
ESP_LOGCONFIG(TAG, "BMP3XX:");
ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
LOG_I2C_DEVICE(this);
switch (this->error_code_) {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BMP3XX failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(
TAG,
"BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
this->chip_id_.reg);
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, "BMP3XX failed to reset");
break;
default:
ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_);
break;
}
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_)));
LOG_UPDATE_INTERVAL(this);
if (this->temperature_sensor_) {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
}
if (this->pressure_sensor_) {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }
void BMP3XXComponent::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request...");
float meas_time = 1.0f;
// Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2
meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f;
meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f;
meas_time += 0.234f;
if (!set_mode(FORCED_MODE)) {
ESP_LOGE(TAG, "Failed start forced mode");
this->mark_failed();
return;
}
ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time)));
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
float temperature = 0.0f;
float pressure = 0.0f;
if (this->pressure_sensor_ != nullptr) {
if (!get_measurements(temperature, pressure)) {
ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update");
this->status_set_warning();
return;
}
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
} else {
if (!get_temperature(temperature)) {
ESP_LOGW(TAG, "Failed to read temperature - skipping update");
this->status_set_warning();
return;
}
ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature);
}
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
this->pressure_sensor_->publish_state(pressure);
this->status_clear_warning();
set_mode(SLEEP_MODE);
});
}
// Reset the BMP3XX
uint8_t BMP3XXComponent::reset() {
write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register
// Wait for 10ms
delay(10);
this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register
return event_.bit.por_detected; // Return if device reset is complete
}
// Start a one shot measurement in FORCED_MODE
bool BMP3XXComponent::start_forced_conversion() {
// Only set FORCED_MODE if we're already in SLEEP_MODE
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
return set_mode(FORCED_MODE);
}
return true;
}
// Stop the conversion and return to SLEEP_MODE
bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); }
// Set the pressure oversampling rate
bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) {
osr_.bit.osr_p = oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Set the temperature oversampling rate
bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) {
osr_.bit.osr_t = oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Set the IIR filter setting
bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) {
config_.bit.iir_filter = iir_filter;
return this->write_byte(BMP388_CONFIG, config_.reg);
}
// Get temperature
bool BMP3XXComponent::get_temperature(float &temperature) {
// Check if a measurement is ready
if (!data_ready()) {
return false;
}
uint8_t data[3];
// Read the temperature
if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) {
ESP_LOGE(TAG, "Failed to read temperature");
return false;
}
// Copy the temperature data into the adc variables
int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
return true;
}
// Get the pressure
bool BMP3XXComponent::get_pressure(float &pressure) {
float temperature;
return get_measurements(temperature, pressure);
}
// Get temperature and pressure
bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
// Check if a measurement is ready
if (!data_ready()) {
ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update");
return false;
}
uint8_t data[6];
// Read the temperature and pressure data
if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) {
ESP_LOGE(TAG, "Failed to read measurements");
return false;
}
// Copy the temperature and pressure data into the adc variables
int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
// Pressure compensation (function from BMP388 datasheet)
pressure = bmp388_compensate_pressure_((float) adc_pres, temperature);
// Calculate the pressure in millibar/hPa
pressure /= 100.0f;
return true;
}
// Set the BMP388's mode in the power control register
bool BMP3XXComponent::set_mode(OperationMode mode) {
pwr_ctrl_.bit.mode = mode;
return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg);
}
// Set the BMP388 oversampling register
bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling,
Oversampling temperature_oversampling) {
osr_.reg = temperature_oversampling << 3 | pressure_oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Check if measurement data is ready
bool BMP3XXComponent::data_ready() {
// If we're in SLEEP_MODE return immediately
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
ESP_LOGD(TAG, "Not ready - sensor is in sleep mode");
return false;
}
// Read the interrupt status register
uint8_t status;
if (!this->read_byte(BMP388_INT_STATUS, &status)) {
ESP_LOGE(TAG, "Failed to read status register");
return false;
}
int_status_.reg = status;
ESP_LOGVV(TAG, "data ready status %d", status);
// If we're in FORCED_MODE switch back to SLEEP_MODE
if (int_status_.bit.drdy) {
if (pwr_ctrl_.bit.mode == FORCED_MODE) {
pwr_ctrl_.bit.mode = SLEEP_MODE;
}
return true; // The measurement is ready
}
return false; // The measurement is still pending
}
////////////////////////////////////////////////////////////////////////////////
// Bosch BMP3XXComponent (Private) Member Functions
////////////////////////////////////////////////////////////////////////////////
float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) {
float partial_data1 = uncomp_temp - compensation_float_params_.param_T1;
float partial_data2 = partial_data1 * compensation_float_params_.param_T2;
return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3;
}
float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) {
float partial_data1 = compensation_float_params_.param_P6 * t_lin;
float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin;
float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin;
float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3;
partial_data1 = compensation_float_params_.param_P2 * t_lin;
partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin;
partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin;
float partial_out2 =
uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3);
partial_data1 = uncomp_press * uncomp_press;
partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin;
partial_data3 = partial_data1 * partial_data2;
float partial_data4 =
partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11;
return partial_out1 + partial_out2 + partial_data4;
}
} // namespace bmp3xx
} // namespace esphome

View File

@@ -0,0 +1,237 @@
/*
based on BMP388_DEV by Martin Lindupp
under MIT License (MIT)
Copyright (C) Martin Lindupp 2020
http://github.com/MartinL1/BMP388_DEV
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bmp3xx {
static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID
static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID
static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code
/// BMP388_DEV Registers
enum {
BMP388_CHIP_ID = 0x00, // Chip ID register sub-address
BMP388_ERR_REG = 0x02, // Error register sub-address
BMP388_STATUS = 0x03, // Status register sub-address
BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address
BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address
BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address
BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address
BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address
BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address
BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address
BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address
BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address
BMP388_EVENT = 0x10, // Event register sub-address
BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address
BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address
BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address
BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address
BMP388_OSR = 0x1C, // Oversampling register sub-address
BMP388_ODR = 0x1D, // Output Data Rate register sub-address
BMP388_CONFIG = 0x1F, // Configuration register sub-address
BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address
BMP388_CMD = 0x7E // Command register sub-address
};
/// Device mode bitfield in the control and measurement register
enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 };
/// Oversampling bit fields in the control and measurement register
enum Oversampling {
OVERSAMPLING_NONE = 0x00,
OVERSAMPLING_X2 = 0x01,
OVERSAMPLING_X4 = 0x02,
OVERSAMPLING_X8 = 0x03,
OVERSAMPLING_X16 = 0x04,
OVERSAMPLING_X32 = 0x05
};
/// Infinite Impulse Response (IIR) filter bit field in the configuration register
enum IIRFilter {
IIR_FILTER_OFF = 0x00,
IIR_FILTER_2 = 0x01,
IIR_FILTER_4 = 0x02,
IIR_FILTER_8 = 0x03,
IIR_FILTER_16 = 0x04,
IIR_FILTER_32 = 0x05,
IIR_FILTER_64 = 0x06,
IIR_FILTER_128 = 0x07
};
/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor.
class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
/// Set the oversampling value for the temperature sensor. Default is 16x.
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
this->temperature_oversampling_ = temperature_oversampling;
}
/// Set the oversampling value for the pressure sensor. Default is 16x.
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
this->pressure_oversampling_ = pressure_oversampling;
}
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
/// Soft reset the sensor
uint8_t reset();
/// Start continuous measurement in NORMAL_MODE
bool start_normal_conversion();
/// Start a one shot measurement in FORCED_MODE
bool start_forced_conversion();
/// Stop the conversion and return to SLEEP_MODE
bool stop_conversion();
/// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32
bool set_pressure_oversampling(Oversampling pressure_oversampling);
/// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32
bool set_temperature_oversampling(Oversampling temperature_oversampling);
/// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32
bool set_iir_filter(IIRFilter iir_filter);
/// Get a temperature measurement
bool get_temperature(float &temperature);
/// Get a pressure measurement
bool get_pressure(float &pressure);
/// Get a temperature and pressure measurement
bool get_measurements(float &temperature, float &pressure);
/// Get a temperature and pressure measurement
bool get_measurement();
/// Set the barometer mode
bool set_mode(OperationMode mode);
/// Set the BMP388 oversampling register
bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling);
/// Checks if a measurement is ready
bool data_ready();
protected:
Oversampling temperature_oversampling_{OVERSAMPLING_X16};
Oversampling pressure_oversampling_{OVERSAMPLING_X16};
IIRFilter iir_filter_{IIR_FILTER_OFF};
OperationMode operation_mode_{FORCED_MODE};
sensor::Sensor *temperature_sensor_;
sensor::Sensor *pressure_sensor_;
enum ErrorCode {
NONE = 0,
ERROR_COMMUNICATION_FAILED,
ERROR_WRONG_CHIP_ID,
ERROR_SENSOR_STATUS,
ERROR_SENSOR_RESET,
} error_code_{NONE};
struct { // The BMP388 compensation trim parameters (coefficients)
uint16_t param_T1;
uint16_t param_T2;
int8_t param_T3;
int16_t param_P1;
int16_t param_P2;
int8_t param_P3;
int8_t param_P4;
uint16_t param_P5;
uint16_t param_P6;
int8_t param_P7;
int8_t param_P8;
int16_t param_P9;
int8_t param_P10;
int8_t param_P11;
} __attribute__((packed)) compensation_params_;
struct FloatParams { // The BMP388 float point compensation trim parameters
float param_T1;
float param_T2;
float param_T3;
float param_P1;
float param_P2;
float param_P3;
float param_P4;
float param_P5;
float param_P6;
float param_P7;
float param_P8;
float param_P9;
float param_P10;
float param_P11;
} compensation_float_params_;
union { // Copy of the BMP388's chip id register
struct {
uint8_t chip_id_nvm : 4;
uint8_t chip_id_fixed : 4;
} bit;
uint8_t reg;
} chip_id_ = {.reg = 0};
union { // Copy of the BMP388's event register
struct {
uint8_t por_detected : 1;
} bit;
uint8_t reg;
} event_ = {.reg = 0};
union { // Copy of the BMP388's interrupt status register
struct {
uint8_t fwm_int : 1;
uint8_t ffull_int : 1;
uint8_t : 1;
uint8_t drdy : 1;
} bit;
uint8_t reg;
} int_status_ = {.reg = 0};
union { // Copy of the BMP388's power control register
struct {
uint8_t press_en : 1;
uint8_t temp_en : 1;
uint8_t : 2;
uint8_t mode : 2;
} bit;
uint8_t reg;
} pwr_ctrl_ = {.reg = 0};
union { // Copy of the BMP388's oversampling register
struct {
uint8_t osr_p : 3;
uint8_t osr_t : 3;
} bit;
uint8_t reg;
} osr_ = {.reg = 0};
union { // Copy of the BMP388's output data rate register
struct {
uint8_t odr_sel : 5;
} bit;
uint8_t reg;
} odr_ = {.reg = 0};
union { // Copy of the BMP388's configuration register
struct {
uint8_t : 1;
uint8_t iir_filter : 3;
} bit;
uint8_t reg;
} config_ = {.reg = 0};
// Bosch temperature compensation function
float bmp388_compensate_temperature_(float uncomp_temp);
// Bosch pressure compensation function
float bmp388_compensate_pressure_(float uncomp_press, float t_lin);
};
} // namespace bmp3xx
} // namespace esphome

View File

@@ -0,0 +1,100 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
)
CODEOWNERS = ["@martgras"]
DEPENDENCIES = ["i2c"]
bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx")
Oversampling = bmp3xx_ns.enum("Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": Oversampling.OVERSAMPLING_NONE,
"2X": Oversampling.OVERSAMPLING_X2,
"4X": Oversampling.OVERSAMPLING_X4,
"8X": Oversampling.OVERSAMPLING_X8,
"16X": Oversampling.OVERSAMPLING_X16,
"32x": Oversampling.OVERSAMPLING_X32,
}
IIRFilter = bmp3xx_ns.enum("IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": IIRFilter.IIR_FILTER_OFF,
"2X": IIRFilter.IIR_FILTER_2,
"4X": IIRFilter.IIR_FILTER_4,
"8X": IIRFilter.IIR_FILTER_8,
"16X": IIRFilter.IIR_FILTER_16,
"32X": IIRFilter.IIR_FILTER_32,
"64X": IIRFilter.IIR_FILTER_64,
"128X": IIRFilter.IIR_FILTER_128,
}
BMP3XXComponent = bmp3xx_ns.class_(
"BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BMP3XXComponent),
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="2X"): 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="16X"): 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(0x77))
)
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)
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
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_config(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_config(conf[CONF_OVERSAMPLING]))

View File

@@ -0,0 +1,53 @@
import esphome.codegen as cg
from esphome import pins
import esphome.config_validation as cv
from esphome.const import (
CONF_DELAY,
CONF_ID,
)
CODEOWNERS = ["@asoehlke"]
AUTO_LOAD = ["sensor", "voltage_sampler"]
cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067")
CD74HC4067Component = cd74hc4067_ns.class_(
"CD74HC4067Component", cg.Component, cg.PollingComponent
)
CONF_PIN_S0 = "pin_s0"
CONF_PIN_S1 = "pin_s1"
CONF_PIN_S2 = "pin_s2"
CONF_PIN_S3 = "pin_s3"
DEFAULT_DELAY = "2ms"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CD74HC4067Component),
cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema,
cv.Optional(
CONF_DELAY, default=DEFAULT_DELAY
): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0])
cg.add(var.set_pin_s0(pin_s0))
pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1])
cg.add(var.set_pin_s1(pin_s1))
pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2])
cg.add(var.set_pin_s2(pin_s2))
pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3])
cg.add(var.set_pin_s3(pin_s3))
cg.add(var.set_switch_delay(config[CONF_DELAY]))

View File

@@ -0,0 +1,86 @@
#include "cd74hc4067.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cd74hc4067 {
static const char *const TAG = "cd74hc4067";
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067...");
this->pin_s0_->setup();
this->pin_s1_->setup();
this->pin_s2_->setup();
this->pin_s3_->setup();
// set other pin, so that activate_pin will really switch
this->active_pin_ = 1;
this->activate_pin(0);
}
void CD74HC4067Component::dump_config() {
ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:");
LOG_PIN(" S0 Pin: ", this->pin_s0_);
LOG_PIN(" S1 Pin: ", this->pin_s1_);
LOG_PIN(" S2 Pin: ", this->pin_s2_);
LOG_PIN(" S3 Pin: ", this->pin_s3_);
ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_);
}
void CD74HC4067Component::activate_pin(uint8_t pin) {
if (this->active_pin_ != pin) {
ESP_LOGD(TAG, "switch to input %d", pin);
static int mux_channel[16][4] = {
{0, 0, 0, 0}, // channel 0
{1, 0, 0, 0}, // channel 1
{0, 1, 0, 0}, // channel 2
{1, 1, 0, 0}, // channel 3
{0, 0, 1, 0}, // channel 4
{1, 0, 1, 0}, // channel 5
{0, 1, 1, 0}, // channel 6
{1, 1, 1, 0}, // channel 7
{0, 0, 0, 1}, // channel 8
{1, 0, 0, 1}, // channel 9
{0, 1, 0, 1}, // channel 10
{1, 1, 0, 1}, // channel 11
{0, 0, 1, 1}, // channel 12
{1, 0, 1, 1}, // channel 13
{0, 1, 1, 1}, // channel 14
{1, 1, 1, 1} // channel 15
};
this->pin_s0_->digital_write(mux_channel[pin][0]);
this->pin_s1_->digital_write(mux_channel[pin][1]);
this->pin_s2_->digital_write(mux_channel[pin][2]);
this->pin_s3_->digital_write(mux_channel[pin][3]);
// small delay is needed to let the multiplexer switch
delay(this->switch_delay_);
this->active_pin_ = pin;
}
}
CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {}
void CD74HC4067Sensor::update() {
float value_v = this->sample();
this->publish_state(value_v);
}
float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; }
float CD74HC4067Sensor::sample() {
this->parent_->activate_pin(this->pin_);
return this->source_->sample();
}
void CD74HC4067Sensor::dump_config() {
LOG_SENSOR(TAG, "CD74HC4067 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
LOG_UPDATE_INTERVAL(this);
}
} // namespace cd74hc4067
} // namespace esphome

View File

@@ -0,0 +1,65 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
namespace esphome {
namespace cd74hc4067 {
class CD74HC4067Component : public Component {
public:
/// Set up the internal sensor array.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
/// setting pin active by setting the right combination of the four multiplexer input pins
void activate_pin(uint8_t pin);
/// set the pin connected to multiplexer control pin 0
void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; }
/// set the pin connected to multiplexer control pin 1
void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; }
/// set the pin connected to multiplexer control pin 2
void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; }
/// set the pin connected to multiplexer control pin 3
void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; }
/// set the delay needed after an input switch
void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; }
private:
InternalGPIOPin *pin_s0_;
InternalGPIOPin *pin_s1_;
InternalGPIOPin *pin_s2_;
InternalGPIOPin *pin_s3_;
/// the currently active pin
uint8_t active_pin_;
uint32_t switch_delay_;
};
class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public:
CD74HC4067Sensor(CD74HC4067Component *parent);
void update() override;
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; }
float sample() override;
protected:
CD74HC4067Component *parent_;
/// The sampling source to read values from.
voltage_sampler::VoltageSampler *source_;
uint8_t pin_;
};
} // namespace cd74hc4067
} // namespace esphome

View File

@@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import (
CONF_ID,
CONF_SENSOR,
CONF_NUMBER,
ICON_FLASH,
UNIT_VOLT,
STATE_CLASS_MEASUREMENT,
DEVICE_CLASS_VOLTAGE,
)
from . import cd74hc4067_ns, CD74HC4067Component
DEPENDENCIES = ["cd74hc4067"]
CD74HC4067Sensor = cd74hc4067_ns.class_(
"CD74HC4067Sensor",
sensor.Sensor,
cg.PollingComponent,
voltage_sampler.VoltageSampler,
)
CONF_CD74HC4067_ID = "cd74hc4067_id"
CONFIG_SCHEMA = (
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_FLASH,
)
.extend(
{
cv.GenerateID(): cv.declare_id(CD74HC4067Sensor),
cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component),
cv.Required(CONF_NUMBER): cv.int_range(0, 15),
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
}
)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_CD74HC4067_ID])
var = cg.new_Pvariable(config[CONF_ID], parent)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_pin(config[CONF_NUMBER]))
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_source(sens))

View File

@@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_)
if (this->supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_)
if (this->supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_dry_)
if (this->supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (supports_fan_only_)
if (this->supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supports_two_point_target_temperature(false);
traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_);
traits.set_visual_temperature_step(this->temperature_step_);
traits.set_supported_fan_modes(fan_modes_);
traits.set_supported_swing_modes(swing_modes_);
traits.set_supported_fan_modes(this->fan_modes_);
traits.set_supported_swing_modes(this->swing_modes_);
traits.set_supported_presets(this->presets_);
return traits;
}
@@ -50,6 +51,7 @@ void ClimateIR::setup() {
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
this->fan_mode = climate::CLIMATE_FAN_AUTO;
this->swing_mode = climate::CLIMATE_SWING_OFF;
this->preset = climate::CLIMATE_PRESET_NONE;
}
// Never send nan to HA
if (std::isnan(this->target_temperature))
@@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) {
this->fan_mode = *call.get_fan_mode();
if (call.get_swing_mode().has_value())
this->swing_mode = *call.get_swing_mode();
if (call.get_preset().has_value())
this->preset = *call.get_preset();
this->transmit_state();
this->publish_state();
}

View File

@@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
std::set<climate::ClimateSwingMode> swing_modes = {}) {
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step;
@@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
this->supports_fan_only_ = supports_fan_only;
this->fan_modes_ = std::move(fan_modes);
this->swing_modes_ = std::move(swing_modes);
this->presets_ = std::move(presets);
}
void setup() override;
@@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
bool supports_fan_only_{false};
std::set<climate::ClimateFanMode> fan_modes_ = {};
std::set<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimatePreset> presets_ = {};
remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr};

View File

@@ -1,4 +1,5 @@
#include "coolix.h"
#include "esphome/components/remote_base/coolix_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -6,29 +7,29 @@ namespace coolix {
static const char *const TAG = "coolix.climate";
const uint32_t COOLIX_OFF = 0xB27BE0;
const uint32_t COOLIX_SWING = 0xB26BE0;
const uint32_t COOLIX_LED = 0xB5F5A5;
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
static const uint32_t COOLIX_OFF = 0xB27BE0;
static const uint32_t COOLIX_SWING = 0xB26BE0;
static const uint32_t COOLIX_LED = 0xB5F5A5;
static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint8_t COOLIX_COOL = 0b0000;
const uint8_t COOLIX_DRY_FAN = 0b0100;
const uint8_t COOLIX_AUTO = 0b1000;
const uint8_t COOLIX_HEAT = 0b1100;
const uint32_t COOLIX_MODE_MASK = 0b1100;
const uint32_t COOLIX_FAN_MASK = 0xF000;
const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
const uint32_t COOLIX_FAN_AUTO = 0xB000;
const uint32_t COOLIX_FAN_MIN = 0x9000;
const uint32_t COOLIX_FAN_MED = 0x5000;
const uint32_t COOLIX_FAN_MAX = 0x3000;
static const uint8_t COOLIX_COOL = 0b0000;
static const uint8_t COOLIX_DRY_FAN = 0b0100;
static const uint8_t COOLIX_AUTO = 0b1000;
static const uint8_t COOLIX_HEAT = 0b1100;
static const uint32_t COOLIX_MODE_MASK = 0b1100;
static const uint32_t COOLIX_FAN_MASK = 0xF000;
static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
static const uint32_t COOLIX_FAN_AUTO = 0xB000;
static const uint32_t COOLIX_FAN_MIN = 0x9000;
static const uint32_t COOLIX_FAN_MED = 0x5000;
static const uint32_t COOLIX_FAN_MAX = 0x3000;
// Temperature
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
static const uint32_t COOLIX_TEMP_MASK = 0b11110000;
static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
0b00000000, // 17C
0b00010000, // 18c
0b00110000, // 19C
@@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
0b10110000 // 30C
};
// Constants
static const uint32_t BIT_MARK_US = 660;
static const uint32_t HEADER_MARK_US = 560 * 8;
static const uint32_t HEADER_SPACE_US = 560 * 8;
static const uint32_t BIT_ONE_SPACE_US = 1500;
static const uint32_t BIT_ZERO_SPACE_US = 450;
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
const uint16_t COOLIX_BITS = 24;
void CoolixClimate::transmit_state() {
uint32_t remote_state = 0xB20F00;
@@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() {
}
}
}
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state);
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
data->set_carrier_frequency(38000);
uint16_t repeat = 1;
for (uint16_t r = 0; r <= repeat; r++) {
// Header
data->mark(HEADER_MARK_US);
data->space(HEADER_SPACE_US);
// Data
// Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
// Grab a bytes worth of data.
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
// Normal
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
data->mark(BIT_MARK_US);
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
}
// Inverted
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
data->mark(BIT_MARK_US);
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
}
}
// Footer
data->mark(BIT_MARK_US);
data->space(FOOTER_SPACE_US); // Pause before repeating
}
remote_base::CoolixProtocol().encode(data, remote_state);
transmit.perform();
}
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {
auto decoded = remote_base::CoolixProtocol().decode(data);
if (!decoded.has_value())
return false;
// Decoded remote state y 3 bytes long code.
uint32_t remote_state = 0;
// The protocol sends the data twice, read here
uint32_t loop_read;
for (uint16_t loop = 1; loop <= 2; loop++) {
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
return false;
loop_read = 0;
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
uint8_t byte = 0;
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
byte |= 1 << a_bit;
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
return false;
}
// Need to see this segment inverted
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
bool bit = byte & (1 << a_bit);
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
return false;
}
// Receiving MSB first: reorder bytes
loop_read |= byte << ((2 - a_byte) * 8);
}
// Footer Mark
if (!data.expect_mark(BIT_MARK_US))
return false;
if (loop == 1) {
// Back up state on first loop
remote_state = loop_read;
if (!data.expect_space(FOOTER_SPACE_US))
return false;
}
}
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
uint32_t remote_state = *decoded;
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
if ((remote_state & 0xFF0000) != 0xB20000)
return false;
if (remote_state == COOLIX_OFF) {
this->mode = climate::CLIMATE_MODE_OFF;
parent->mode = climate::CLIMATE_MODE_OFF;
} else if (remote_state == COOLIX_SWING) {
this->swing_mode =
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
parent->swing_mode =
parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
this->mode = climate::CLIMATE_MODE_HEAT;
parent->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
this->mode = climate::CLIMATE_MODE_DRY;
parent->mode = climate::CLIMATE_MODE_DRY;
else
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
} else
this->mode = climate::CLIMATE_MODE_COOL;
parent->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
this->mode == climate::CLIMATE_MODE_DRY)
this->fan_mode = climate::CLIMATE_FAN_AUTO;
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
parent->mode == climate::CLIMATE_MODE_DRY)
parent->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
this->fan_mode = climate::CLIMATE_FAN_LOW;
parent->fan_mode = climate::CLIMATE_FAN_LOW;
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
this->fan_mode = climate::CLIMATE_FAN_HIGH;
parent->fan_mode = climate::CLIMATE_FAN_HIGH;
// Temperature
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
if (COOLIX_TEMP_MAP[i] == temperature_code)
this->target_temperature = i + COOLIX_TEMP_MIN;
parent->target_temperature = i + COOLIX_TEMP_MIN;
}
this->publish_state();
parent->publish_state();
return true;
}

View File

@@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR {
climate_ir::ClimateIR::control(call);
}
/// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for
/// example.
static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data);
protected:
/// Transmit via IR the state of this climate controller.
void transmit_state() override;
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); }
bool send_swing_cmd_{false};
};

View File

@@ -210,7 +210,10 @@ Cover::Cover() : Cover("") {}
std::string Cover::get_device_class() {
if (this->device_class_override_.has_value())
return *this->device_class_override_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->device_class();
#pragma GCC diagnostic pop
}
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }

View File

@@ -169,6 +169,11 @@ class Cover : public EntityBase {
friend CoverCall;
virtual void control(const CoverCall &call) = 0;
/** Override this to set the default device class.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual std::string device_class();
optional<CoverRestoreState> restore_state_();

View File

@@ -38,10 +38,9 @@ void DallasComponent::setup() {
raw_sensors = this->one_wire_->search_vec();
for (auto &address : raw_sensors) {
std::string s = uint64_to_string(address);
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", s.c_str());
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
continue;
}
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
@@ -77,8 +76,7 @@ void DallasComponent::dump_config() {
} else {
ESP_LOGD(TAG, " Found sensors:");
for (auto &address : this->found_sensors_) {
std::string s = uint64_to_string(address);
ESP_LOGD(TAG, " 0x%s", s.c_str());
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
}
}
@@ -147,7 +145,7 @@ void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
const std::string &DallasTemperatureSensor::get_address_name() {
if (this->address_name_.empty()) {
this->address_name_ = std::string("0x") + uint64_to_string(this->address_);
this->address_name_ = std::string("0x") + format_hex(this->address_);
}
return this->address_name_;
@@ -237,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() {
return temp / 128.0f;
}
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + uint64_to_string(this->address_); }
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); }
} // namespace dallas
} // namespace esphome

View File

@@ -3,6 +3,7 @@ from pathlib import Path
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components.packages import validate_source_shorthand
from esphome.wizard import wizard_file
from esphome.yaml_util import dump
@@ -48,12 +49,24 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
if p.exists():
raise FileExistsError
config = {
"substitutions": {"name": name},
"packages": {project_name: import_url},
"esphome": {"name_add_mac_suffix": False},
}
p.write_text(
dump(config) + WIFI_CONFIG,
encoding="utf8",
)
if project_name == "esphome.web":
p.write_text(
wizard_file(
name=name,
platform="ESP32" if "esp32" in import_url else "ESP8266",
board="esp32dev" if "esp32" in import_url else "esp01_1m",
ssid="!secret wifi_ssid",
psk="!secret wifi_password",
),
encoding="utf8",
)
else:
config = {
"substitutions": {"name": name},
"packages": {project_name: import_url},
"esphome": {"name_add_mac_suffix": False},
}
p.write_text(
dump(config) + WIFI_CONFIG,
encoding="utf8",
)

View File

@@ -101,7 +101,7 @@ void DebugComponent::dump_config() {
info.features &= ~CHIP_FEATURE_BT;
}
if (info.features)
features += "Other:" + uint64_to_string(info.features);
features += "Other:" + format_hex(info.features);
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
info.revision);

View File

@@ -33,7 +33,7 @@ DHT = dht_ns.class_("DHT", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DHT),
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,

View File

@@ -5,6 +5,7 @@
#include "esphome/core/color.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace display {
@@ -15,7 +16,8 @@ const Color COLOR_OFF(0, 0, 0, 0);
const Color COLOR_ON(255, 255, 255, 255);
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(buffer_length);
if (this->buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate buffer for display!");
return;

View File

@@ -2,8 +2,9 @@ from dataclasses import dataclass
from typing import Union
from pathlib import Path
import logging
import os
from esphome.helpers import write_file_if_changed
from esphome.helpers import copy_file_if_changed, write_file_if_changed
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
@@ -295,6 +296,8 @@ async def to_code(config):
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF")
@@ -412,3 +415,10 @@ def copy_files():
CORE.relative_build_path("partitions.csv"),
IDF_PARTITIONS_CSV,
)
dir = os.path.dirname(__file__)
post_build_file = os.path.join(dir, "post_build.py.script")
copy_file_if_changed(
post_build_file,
CORE.relative_build_path("post_build.py"),
)

View File

@@ -261,6 +261,37 @@ ESP32_BOARD_PINS = {
"SS": 33,
"TX": 17,
},
"featheresp32-s2": {
"SDA": 3,
"SCL": 4,
"SS": 42,
"MOSI": 35,
"SCK": 36,
"MISO": 37,
"A0": 18,
"A1": 17,
"A10": 27,
"A11": 12,
"A12": 13,
"A13": 35,
"A2": 16,
"A3": 15,
"A4": 14,
"A5": 8,
"LED": 13,
"TX": 39,
"RX": 38,
"T5": 5,
"T8": 8,
"T9": 9,
"T10": 10,
"T11": 11,
"T12": 12,
"T13": 13,
"T14": 14,
"DAC1": 17,
"DAC2": 18,
},
"firebeetle32": {"LED": 2},
"fm-devkit": {
"D0": 34,

View File

@@ -0,0 +1,43 @@
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
import esptool
# pylint: disable=E0602
Import("env") # noqa
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
cmd = [
"--chip",
chip,
"merge_bin",
"-o",
new_file_name,
"--flash_size",
flash_size,
]
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print()
print(f"Using esptool.py arguments: {' '.join(cmd)}")
print()
esptool.main(cmd)
# pylint: disable=E0602
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa

View File

@@ -19,6 +19,8 @@ from esphome.cpp_helpers import setup_entity
DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["psram"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
@@ -153,9 +155,7 @@ async def to_code(config):
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
cg.add_define("USE_ESP32_CAMERA")
cg.add_build_flag("-DBOARD_HAS_PSRAM")
if CORE.using_esp_idf:
cg.add_library("espressif/esp32-camera", "1.0.0")
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True)

View File

@@ -49,9 +49,6 @@ void ESP32Camera::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
#ifdef USE_ARDUINO
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
#endif // USE_ARDUINO
ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1,
conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7);
ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync);
@@ -136,6 +133,13 @@ void ESP32Camera::loop() {
this->current_image_.reset();
}
// request idle image every idle_update_interval
const uint32_t now = millis();
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
this->last_idle_request_ = now;
this->request_image(IDLE);
}
// Check if we should fetch a new image
if (!this->has_requested_image_())
return;
@@ -143,7 +147,6 @@ void ESP32Camera::loop() {
// image is still in use
return;
}
const uint32_t now = millis();
if (now - this->last_update_ <= this->max_update_interval_)
return;
@@ -160,12 +163,12 @@ void ESP32Camera::loop() {
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
return;
}
this->current_image_ = std::make_shared<CameraImage>(fb);
this->current_image_ = std::make_shared<CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
this->new_image_callback_.call(this->current_image_);
this->last_update_ = now;
this->single_requester_ = false;
this->single_requesters_ = 0;
}
void ESP32Camera::framebuffer_task(void *pv) {
while (true) {
@@ -261,24 +264,10 @@ void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightnes
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
void ESP32Camera::request_image() { this->single_requester_ = true; }
void ESP32Camera::request_stream() { this->last_stream_request_ = millis(); }
bool ESP32Camera::has_requested_image_() const {
if (this->single_requester_)
// single request
return true;
uint32_t now = millis();
if (now - this->last_stream_request_ < 5000)
// stream request
return true;
if (this->idle_update_interval_ != 0 && now - this->last_update_ > this->idle_update_interval_)
// idle update
return true;
return false;
}
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; }
void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; }
void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); }
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
this->max_update_interval_ = max_update_interval;
@@ -307,7 +296,10 @@ uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_b
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
size_t CameraImage::get_data_length() { return this->buffer_->len; }
CameraImage::CameraImage(camera_fb_t *buffer) : buffer_(buffer) {}
bool CameraImage::was_requested_by(CameraRequester requester) const {
return (this->requesters_ & (1 << requester)) != 0;
}
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
} // namespace esp32_camera
} // namespace esphome

View File

@@ -14,15 +14,19 @@ namespace esp32_camera {
class ESP32Camera;
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
class CameraImage {
public:
CameraImage(camera_fb_t *buffer);
CameraImage(camera_fb_t *buffer, uint8_t requester);
camera_fb_t *get_raw_buffer();
uint8_t *get_data_buffer();
size_t get_data_length();
bool was_requested_by(CameraRequester requester) const;
protected:
camera_fb_t *buffer_;
uint8_t requesters_;
};
class CameraImageReader {
@@ -81,8 +85,9 @@ class ESP32Camera : public Component, public EntityBase {
void dump_config() override;
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
float get_setup_priority() const override;
void request_stream();
void request_image();
void start_stream(CameraRequester requester);
void stop_stream(CameraRequester requester);
void request_image(CameraRequester requester);
protected:
uint32_t hash_base() override;
@@ -104,13 +109,14 @@ class ESP32Camera : public Component, public EntityBase {
esp_err_t init_error_{ESP_OK};
std::shared_ptr<CameraImage> current_image_;
uint32_t last_stream_request_{0};
bool single_requester_{false};
uint8_t single_requesters_{0};
uint8_t stream_requesters_{0};
QueueHandle_t framebuffer_get_queue_;
QueueHandle_t framebuffer_return_queue_;
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
uint32_t max_update_interval_{1000};
uint32_t idle_update_interval_{15000};
uint32_t last_idle_request_{0};
uint32_t last_update_{0};
};

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace esp32_camera_web_server {
static const int IMAGE_REQUEST_TIMEOUT = 2000;
static const int IMAGE_REQUEST_TIMEOUT = 5000;
static const char *const TAG = "esp32_camera_web_server";
#define PART_BOUNDARY "123456789000000000000987654321"
@@ -68,7 +68,7 @@ void CameraWebServer::setup() {
httpd_register_uri_handler(this->httpd_, &uri);
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
if (this->running_) {
if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) {
this->image_ = std::move(image);
xSemaphoreGive(this->semaphore_);
}
@@ -169,11 +169,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
uint32_t last_frame = millis();
uint32_t frames = 0;
while (res == ESP_OK && this->running_) {
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_stream();
}
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER);
while (res == ESP_OK && this->running_) {
auto image = this->wait_for_image_();
if (!image) {
@@ -204,6 +202,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
}
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
return res;
@@ -212,9 +212,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK;
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_image();
}
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER);
auto image = this->wait_for_image_();
@@ -233,9 +231,6 @@ esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
if (res == ESP_OK) {
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
}
if (res == ESP_OK) {
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
}

View File

View File

@@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import canbus
from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN
from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE
CODEOWNERS = ["@Sympatron"]
DEPENDENCIES = ["esp32"]
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
# Currently the driver only supports a subset of the bit rates defined in canbus
CAN_SPEEDS = {
"50KBPS": CanSpeed.CAN_50KBPS,
"100KBPS": CanSpeed.CAN_100KBPS,
"125KBPS": CanSpeed.CAN_125KBPS,
"250KBPS": CanSpeed.CAN_250KBPS,
"500KBPS": CanSpeed.CAN_500KBPS,
"1000KBPS": CanSpeed.CAN_1000KBPS,
}
CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(esp32_can),
cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await canbus.register_canbus(var, config)
cg.add(var.set_rx(config[CONF_RX_PIN]))
cg.add(var.set_tx(config[CONF_TX_PIN]))

View File

@@ -0,0 +1,123 @@
#ifdef USE_ESP32
#include "esp32_can.h"
#include "esphome/core/log.h"
#include <driver/can.h>
// WORKAROUND, because CAN_IO_UNUSED is just defined as (-1) in this version
// of the framework which does not work with -fpermissive
#undef CAN_IO_UNUSED
#define CAN_IO_UNUSED ((gpio_num_t) -1)
namespace esphome {
namespace esp32_can {
static const char *const TAG = "esp32_can";
static bool get_bitrate(canbus::CanSpeed bitrate, can_timing_config_t *t_config) {
switch (bitrate) {
case canbus::CAN_50KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_50KBITS();
return true;
case canbus::CAN_100KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_100KBITS();
return true;
case canbus::CAN_125KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_125KBITS();
return true;
case canbus::CAN_250KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_250KBITS();
return true;
case canbus::CAN_500KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_500KBITS();
return true;
case canbus::CAN_1000KBPS:
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_1MBITS();
return true;
default:
return false;
}
}
bool ESP32Can::setup_internal() {
can_general_config_t g_config =
CAN_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, CAN_MODE_NORMAL);
can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
can_timing_config_t t_config;
if (!get_bitrate(this->bit_rate_, &t_config)) {
// invalid bit rate
this->mark_failed();
return false;
}
// Install CAN driver
if (can_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
// Failed to install driver
this->mark_failed();
return false;
}
// Start CAN driver
if (can_start() != ESP_OK) {
// Failed to start driver
this->mark_failed();
return false;
}
return true;
}
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
return canbus::ERROR_FAILTX;
}
uint32_t flags = CAN_MSG_FLAG_NONE;
if (frame->use_extended_id) {
flags |= CAN_MSG_FLAG_EXTD;
}
if (frame->remote_transmission_request) {
flags |= CAN_MSG_FLAG_RTR;
}
can_message_t message = {
.flags = flags,
.identifier = frame->can_id,
.data_length_code = frame->can_data_length_code,
};
if (!frame->remote_transmission_request) {
memcpy(message.data, frame->data, frame->can_data_length_code);
}
if (can_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
return canbus::ERROR_OK;
} else {
return canbus::ERROR_ALLTXBUSY;
}
}
canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) {
can_message_t message;
if (can_receive(&message, 0) != ESP_OK) {
return canbus::ERROR_NOMSG;
}
frame->can_id = message.identifier;
frame->use_extended_id = message.flags & CAN_MSG_FLAG_EXTD;
frame->remote_transmission_request = message.flags & CAN_MSG_FLAG_RTR;
frame->can_data_length_code = message.data_length_code;
if (!frame->remote_transmission_request) {
size_t dlc =
message.data_length_code < canbus::CAN_MAX_DATA_LENGTH ? message.data_length_code : canbus::CAN_MAX_DATA_LENGTH;
memcpy(frame->data, message.data, dlc);
}
return canbus::ERROR_OK;
}
} // namespace esp32_can
} // namespace esphome
#endif

View File

@@ -0,0 +1,29 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/canbus/canbus.h"
#include "esphome/core/component.h"
namespace esphome {
namespace esp32_can {
class ESP32Can : public canbus::Canbus {
public:
void set_rx(int rx) { rx_ = rx; }
void set_tx(int tx) { tx_ = tx; }
ESP32Can(){};
protected:
bool setup_internal() override;
canbus::Error send_message(struct canbus::CanFrame *frame) override;
canbus::Error read_message(struct canbus::CanFrame *frame) override;
int rx_{-1};
int tx_{-1};
};
} // namespace esp32_can
} // namespace esphome
#endif

View File

@@ -1,4 +1,5 @@
import logging
import os
from esphome.const import (
CONF_BOARD,
@@ -14,6 +15,7 @@ from esphome.const import (
from esphome.core import CORE, coroutine_with_priority
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.helpers import copy_file_if_changed
from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns
from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS
@@ -158,6 +160,8 @@ async def to_code(config):
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "ESP8266")
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
@@ -210,3 +214,14 @@ async def to_code(config):
if ld_script is not None:
cg.add_platformio_option("board_build.ldscript", ld_script)
# Called by writer.py
def copy_files():
dir = os.path.dirname(__file__)
post_build_file = os.path.join(dir, "post_build.py.script")
copy_file_if_changed(
post_build_file,
CORE.relative_build_path("post_build.py"),
)

View File

@@ -0,0 +1,15 @@
import shutil
# pylint: disable=E0602
Import("env") # noqa
def esp8266_copy_factory_bin(source, target, env):
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
shutil.copyfile(firmware_name, new_file_name)
# pylint: disable=E0602
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa

View File

@@ -0,0 +1,69 @@
#include "growatt_solar.h"
#include "esphome/core/log.h"
namespace esphome {
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;
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
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 {
if (sensor == nullptr)
return;
float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit;
sensor->publish_state(value);
};
auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void {
float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) +
encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) *
unit;
if (sensor != nullptr)
sensor->publish_state(value);
};
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_, 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_[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_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);
}
void GrowattSolar::dump_config() {
ESP_LOGCONFIG(TAG, "GROWATT Solar:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
}
} // namespace growatt_solar
} // namespace esphome

View File

@@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome {
namespace growatt_solar {
static const float TWO_DEC_UNIT = 0.01;
static const float ONE_DEC_UNIT = 0.1;
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_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; }
void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; }
void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; }
void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; }
void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; }
void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) {
this->phases_[phase].voltage_sensor_ = voltage_sensor;
}
void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) {
this->phases_[phase].current_sensor_ = current_sensor;
}
void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) {
this->phases_[phase].active_power_sensor_ = active_power_sensor;
}
void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) {
this->pvs_[pv].voltage_sensor_ = voltage_sensor;
}
void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) {
this->pvs_[pv].current_sensor_ = current_sensor;
}
void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) {
this->pvs_[pv].active_power_sensor_ = active_power_sensor;
}
protected:
struct GrowattPhase {
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *active_power_sensor_{nullptr};
} phases_[3];
struct GrowattPV {
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *active_power_sensor_{nullptr};
} pvs_[2];
sensor::Sensor *inverter_status_{nullptr};
sensor::Sensor *grid_frequency_sensor_{nullptr};
sensor::Sensor *grid_active_power_sensor_{nullptr};
sensor::Sensor *pv_active_power_sensor_{nullptr};
sensor::Sensor *today_production_{nullptr};
sensor::Sensor *total_energy_production_{nullptr};
sensor::Sensor *inverter_module_temp_{nullptr};
};
} // namespace growatt_solar
} // namespace esphome

View File

@@ -0,0 +1,201 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, modbus
from esphome.const import (
CONF_ACTIVE_POWER,
CONF_CURRENT,
CONF_FREQUENCY,
CONF_ID,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_WATT,
)
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
CONF_TODAY_GENERATION_TIME = "today_generation_time"
CONF_PV1 = "pv1"
CONF_PV2 = "pv2"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_HOURS = "h"
UNIT_KOHM = ""
UNIT_MILLIAMPERE = "mA"
CONF_INVERTER_STATUS = "inverter_status"
CONF_PV_ACTIVE_POWER = "pv_active_power"
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
AUTO_LOAD = ["modbus"]
CODEOWNERS = ["@leeuwte"]
growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar")
GrowattSolar = growatt_solar_ns.class_(
"GrowattSolar", cg.PollingComponent, modbus.ModbusDevice
)
PHASE_SENSORS = {
CONF_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
),
CONF_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_ACTIVE_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
PV_SENSORS = {
CONF_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
),
CONF_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_ACTIVE_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
PHASE_SCHEMA = cv.Schema(
{cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()}
)
PV_SCHEMA = cv.Schema(
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GrowattSolar),
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
cv.Optional(CONF_PV1): PV_SCHEMA,
cv.Optional(CONF_PV2): PV_SCHEMA,
cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PV_ACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("10s"))
.extend(modbus.modbus_device_schema(0x01))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await modbus.register_modbus_device(var, config)
if CONF_INVERTER_STATUS in config:
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
cg.add(var.set_inverter_status_sensor(sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_grid_frequency_sensor(sens))
if CONF_ACTIVE_POWER in config:
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
cg.add(var.set_grid_active_power_sensor(sens))
if CONF_PV_ACTIVE_POWER in config:
sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER])
cg.add(var.set_pv_active_power_sensor(sens))
if CONF_ENERGY_PRODUCTION_DAY in config:
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
cg.add(var.set_today_production_sensor(sens))
if CONF_TOTAL_ENERGY_PRODUCTION in config:
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
cg.add(var.set_total_energy_production_sensor(sens))
if CONF_INVERTER_MODULE_TEMP in config:
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
cg.add(var.set_inverter_module_temp_sensor(sens))
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
if phase not in config:
continue
phase_config = config[phase]
for sensor_type in PHASE_SENSORS:
if sensor_type in phase_config:
sens = await sensor.new_sensor(phase_config[sensor_type])
cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens))
for i, pv in enumerate([CONF_PV1, CONF_PV2]):
if pv not in config:
continue
pv_config = config[pv]
for sensor_type in pv_config:
if sensor_type in pv_config:
sens = await sensor.new_sensor(pv_config[sensor_type])
cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens))

View File

@@ -21,7 +21,9 @@ void HDC1080Component::setup() {
};
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) {
this->mark_failed();
// as instruction is same as powerup defaults (for now), interpret as warning if this fails
ESP_LOGW(TAG, "HDC1080 initial config instruction error");
this->status_set_warning();
return;
}
}

View File

@@ -109,8 +109,8 @@ def to_code(config):
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
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

View File

@@ -172,7 +172,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
if CONF_JSON in config:
json_ = config[CONF_JSON]
if isinstance(json_, Lambda):
args_ = args + [(cg.JsonObjectRef, "root")]
args_ = args + [(cg.JsonObject, "root")]
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
cg.add(var.set_json(lambda_))
else:

View File

@@ -115,8 +115,11 @@ void HttpRequestComponent::close() {
}
const char *HttpRequestComponent::get_string() {
static const String STR = this->client_.getString();
return STR.c_str();
// The static variable is here because HTTPClient::getString() returns a String on ESP32, and we need something to
// to keep a buffer alive.
static std::string str;
str = this->client_.getString().c_str();
return str.c_str();
}
} // namespace http_request

View File

@@ -78,7 +78,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
void set_json(std::function<void(Ts..., JsonObject &)> json_func) { this->json_func_ = json_func; }
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
@@ -118,17 +118,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
}
protected:
void encode_json_(Ts... x, JsonObject &root) {
void encode_json_(Ts... x, JsonObject root) {
for (const auto &item : this->json_) {
auto val = item.second;
root[item.first] = val.value(x...);
}
}
void encode_json_func_(Ts... x, JsonObject &root) { this->json_func_(x..., root); }
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
HttpRequestComponent *parent_;
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject &)> json_func_{nullptr};
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
std::vector<HttpRequestResponseTrigger *> response_triggers_;
};

View File

@@ -25,7 +25,7 @@ void ArduinoI2CBus::setup() {
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
#endif
wire_->begin(sda_pin_, scl_pin_);
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
wire_->setClock(frequency_);
initialized_ = true;
if (this->scan_) {

View File

View File

@@ -0,0 +1,128 @@
#include "ina260.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ina260 {
static const char *const TAG = "ina260";
// | A0 | A1 | Address |
// | GND | GND | 0x40 |
// | GND | V_S+ | 0x41 |
// | GND | SDA | 0x42 |
// | GND | SCL | 0x43 |
// | V_S+ | GND | 0x44 |
// | V_S+ | V_S+ | 0x45 |
// | V_S+ | SDA | 0x46 |
// | V_S+ | SCL | 0x47 |
// | SDA | GND | 0x48 |
// | SDA | V_S+ | 0x49 |
// | SDA | SDA | 0x4A |
// | SDA | SCL | 0x4B |
// | SCL | GND | 0x4C |
// | SCL | V_S+ | 0x4D |
// | SCL | SDA | 0x4E |
// | SCL | SCL | 0x4F |
static const uint8_t INA260_REGISTER_CONFIG = 0x00;
static const uint8_t INA260_REGISTER_CURRENT = 0x01;
static const uint8_t INA260_REGISTER_BUS_VOLTAGE = 0x02;
static const uint8_t INA260_REGISTER_POWER = 0x03;
static const uint8_t INA260_REGISTER_MASK_ENABLE = 0x06;
static const uint8_t INA260_REGISTER_ALERT_LIMIT = 0x07;
static const uint8_t INA260_REGISTER_MANUFACTURE_ID = 0xFE;
static const uint8_t INA260_REGISTER_DEVICE_ID = 0xFF;
void INA260Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up INA260...");
// Reset device on setup
if (!this->write_byte_16(INA260_REGISTER_CONFIG, 0x8000)) {
this->error_code_ = DEVICE_RESET_FAILED;
this->mark_failed();
return;
}
delay(2);
this->read_byte_16(INA260_REGISTER_MANUFACTURE_ID, &this->manufacture_id_);
this->read_byte_16(INA260_REGISTER_DEVICE_ID, &this->device_id_);
if (this->manufacture_id_ != (uint16_t) 0x5449 || this->device_id_ != (uint16_t) 0x2270) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (!this->write_byte_16(INA260_REGISTER_CONFIG, (uint16_t) 0b0000001100000111)) {
this->error_code_ = FAILED_TO_UPDATE_CONFIGURATION;
this->mark_failed();
return;
}
}
void INA260Component::dump_config() {
ESP_LOGCONFIG(TAG, "INA260:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_);
ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_);
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Connected device does not match a known INA260 sensor");
break;
case DEVICE_RESET_FAILED:
ESP_LOGE(TAG, "Device reset failed - Is the device connected?");
break;
case FAILED_TO_UPDATE_CONFIGURATION:
ESP_LOGE(TAG, "Failed to update device configuration");
break;
case NONE:
default:
break;
}
}
void INA260Component::update() {
if (this->bus_voltage_sensor_ != nullptr) {
uint16_t raw_bus_voltage;
if (!this->read_byte_16(INA260_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) {
this->status_set_warning();
return;
}
float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f;
this->bus_voltage_sensor_->publish_state(bus_voltage_v);
}
if (this->current_sensor_ != nullptr) {
uint16_t raw_current;
if (!this->read_byte_16(INA260_REGISTER_CURRENT, &raw_current)) {
this->status_set_warning();
return;
}
float current_a = int16_t(raw_current) * 0.00125f;
this->current_sensor_->publish_state(current_a);
}
if (this->power_sensor_ != nullptr) {
uint16_t raw_power;
if (!this->read_byte_16(INA260_REGISTER_POWER, &raw_power)) {
this->status_set_warning();
return;
}
float power_w = ((int16_t(raw_power) * 10.0f) / 1000.0f);
this->power_sensor_->publish_state(power_w);
}
this->status_clear_warning();
}
} // namespace ina260
} // namespace esphome

View File

@@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ina260 {
class INA260Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { this->bus_voltage_sensor_ = bus_voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; }
protected:
uint16_t manufacture_id_{0};
uint16_t device_id_{0};
sensor::Sensor *bus_voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
enum ErrorCode {
NONE,
COMMUNICATION_FAILED,
DEVICE_RESET_FAILED,
FAILED_TO_UPDATE_CONFIGURATION,
} error_code_{NONE};
};
} // namespace ina260
} // namespace esphome

View File

@@ -0,0 +1,71 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_BUS_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@MrEditor97"]
ina260_ns = cg.esphome_ns.namespace("ina260")
INA260Component = ina260_ns.class_(
"INA260Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(INA260Component),
cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_BUS_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE])
cg.add(var.set_bus_voltage_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))

View File

@@ -6,11 +6,13 @@ from esphome.const import (
CONF_FULL_UPDATE_EVERY,
CONF_ID,
CONF_LAMBDA,
CONF_MODEL,
CONF_PAGES,
CONF_WAKEUP_PIN,
)
DEPENDENCIES = ["i2c", "esp32"]
AUTO_LOAD = ["psram"]
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"
@@ -40,6 +42,13 @@ Inkplate6 = inkplate6_ns.class_(
"Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer
)
InkplateModel = inkplate6_ns.enum("InkplateModel")
MODELS = {
"inkplate_6": InkplateModel.INKPLATE_6,
"inkplate_10": InkplateModel.INKPLATE_10,
}
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
@@ -47,6 +56,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
cv.Optional(CONF_MODEL, default="inkplate_6"): cv.enum(
MODELS, lower=True, space="_"
),
# Control pins
cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
@@ -110,6 +122,8 @@ async def to_code(config):
cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
cg.add(var.set_model(config[CONF_MODEL]))
ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN])
cg.add(var.set_ckv_pin(ckv))
@@ -166,5 +180,3 @@ async def to_code(config):
display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
cg.add(var.set_display_data_7_pin(display_data_7))
cg.add_build_flag("-DBOARD_HAS_PSRAM")

View File

@@ -42,32 +42,32 @@ void Inkplate6::setup() {
this->display();
}
void Inkplate6::initialize_() {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
uint32_t buffer_size = this->get_buffer_length_();
if (buffer_size == 0)
return;
if (this->partial_buffer_ != nullptr) {
free(this->partial_buffer_); // NOLINT
}
if (this->partial_buffer_2_ != nullptr) {
free(this->partial_buffer_2_); // NOLINT
}
if (this->buffer_ != nullptr) {
free(this->buffer_); // NOLINT
}
if (this->partial_buffer_ != nullptr)
allocator.deallocate(this->partial_buffer_, buffer_size);
if (this->partial_buffer_2_ != nullptr)
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
if (this->buffer_ != nullptr)
allocator.deallocate(this->buffer_, buffer_size);
this->buffer_ = (uint8_t *) ps_malloc(buffer_size);
this->buffer_ = allocator.allocate(buffer_size);
if (this->buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate buffer for display!");
this->mark_failed();
return;
}
if (!this->greyscale_) {
this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size);
this->partial_buffer_ = allocator.allocate(buffer_size);
if (this->partial_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
this->mark_failed();
return;
}
this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2);
this->partial_buffer_2_ = allocator.allocate(buffer_size * 2);
if (this->partial_buffer_2_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!");
this->mark_failed();

View File

@@ -10,6 +10,11 @@
namespace esphome {
namespace inkplate6 {
enum InkplateModel : uint8_t {
INKPLATE_6 = 0,
INKPLATE_10 = 1,
};
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
public:
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
@@ -43,6 +48,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
void set_model(InkplateModel model) { this->model_ = model; }
void set_display_data_0_pin(InternalGPIOPin *data) { this->display_data_0_pin_ = data; }
void set_display_data_1_pin(InternalGPIOPin *data) { this->display_data_1_pin_ = data; }
void set_display_data_2_pin(InternalGPIOPin *data) { this->display_data_2_pin_ = data; }
@@ -101,9 +108,21 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
void pins_z_state_();
void pins_as_outputs_();
int get_width_internal() override { return 800; }
int get_width_internal() override {
if (this->model_ == INKPLATE_6)
return 800;
else if (this->model_ == INKPLATE_10)
return 1200;
return 0;
}
int get_height_internal() override { return 600; }
int get_height_internal() override {
if (this->model_ == INKPLATE_6)
return 600;
else if (this->model_ == INKPLATE_10)
return 825;
return 0;
}
size_t get_buffer_length_();
@@ -133,6 +152,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
bool greyscale_;
bool partial_updating_;
InkplateModel model_;
InternalGPIOPin *display_data_0_pin_;
InternalGPIOPin *display_data_1_pin_;
InternalGPIOPin *display_data_2_pin_;

View File

@@ -23,31 +23,6 @@ void IntegrationSensor::setup() {
this->sensor_->add_on_state_callback([this](float state) { this->process_sensor_value_(state); });
}
void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); }
std::string IntegrationSensor::unit_of_measurement() {
std::string suffix;
switch (this->time_) {
case INTEGRATION_SENSOR_TIME_MILLISECOND:
suffix = "ms";
break;
case INTEGRATION_SENSOR_TIME_SECOND:
suffix = "s";
break;
case INTEGRATION_SENSOR_TIME_MINUTE:
suffix = "min";
break;
case INTEGRATION_SENSOR_TIME_HOUR:
suffix = "h";
break;
case INTEGRATION_SENSOR_TIME_DAY:
suffix = "d";
break;
}
std::string base = this->sensor_->get_unit_of_measurement();
if (str_endswith(base, "/" + suffix)) {
return base.substr(0, base.size() - suffix.size() - 1);
}
return base + suffix;
}
void IntegrationSensor::process_sensor_value_(float value) {
const uint32_t now = millis();
const double old_value = this->last_value_;

View File

@@ -63,8 +63,6 @@ class IntegrationSensor : public sensor::Sensor, public Component {
this->last_save_ = now;
this->rtc_.save(&result_f);
}
std::string unit_of_measurement() override;
int8_t accuracy_decimals() override { return this->sensor_->get_accuracy_decimals() + 2; }
sensor::Sensor *sensor_;
IntegrationSensorTime time_;

View File

@@ -2,7 +2,14 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import sensor
from esphome.const import CONF_ICON, CONF_ID, CONF_SENSOR, CONF_RESTORE
from esphome.const import (
CONF_ICON,
CONF_ID,
CONF_SENSOR,
CONF_RESTORE,
CONF_UNIT_OF_MEASUREMENT,
CONF_ACCURACY_DECIMALS,
)
from esphome.core.entity_helpers import inherit_property_from
integration_ns = cg.esphome_ns.namespace("integration")
@@ -30,6 +37,18 @@ CONF_TIME_UNIT = "time_unit"
CONF_INTEGRATION_METHOD = "integration_method"
CONF_MIN_SAVE_INTERVAL = "min_save_interval"
def inherit_unit_of_measurement(uom, config):
suffix = config[CONF_TIME_UNIT]
if uom.endswith("/" + suffix):
return uom[0 : -len("/" + suffix)]
return uom + suffix
def inherit_accuracy_decimals(decimals, config):
return decimals + 2
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(IntegrationSensor),
@@ -51,11 +70,19 @@ FINAL_VALIDATE_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
cv.Optional(CONF_ICON): cv.icon,
cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals,
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
},
extra=cv.ALLOW_EXTRA,
),
inherit_property_from(CONF_ICON, CONF_SENSOR),
inherit_property_from(
CONF_UNIT_OF_MEASUREMENT, CONF_SENSOR, transform=inherit_unit_of_measurement
),
inherit_property_from(
CONF_ACCURACY_DECIMALS, CONF_SENSOR, transform=inherit_accuracy_decimals
),
)

View File

@@ -7,12 +7,11 @@ json_ns = cg.esphome_ns.namespace("json")
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.only_with_arduino,
)
@coroutine_with_priority(1.0)
async def to_code(config):
cg.add_library("ottowinter/ArduinoJson-esphomelib", "5.13.3")
cg.add_library("bblanchon/ArduinoJson", "6.18.5")
cg.add_define("USE_JSON")
cg.add_global(json_ns.using)

View File

@@ -1,8 +1,13 @@
#ifdef USE_ARDUINO
#include "json_util.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
namespace esphome {
namespace json {
@@ -10,110 +15,49 @@ static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT
const char *build_json(const json_build_t &f, size_t *length) {
global_json_buffer.clear();
JsonObject &root = global_json_buffer.createObject();
std::string build_json(const json_build_t &f) {
// Here we are allocating as much heap memory as available minus 2kb to be safe
// 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)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048;
#endif
DynamicJsonDocument json_document(free_heap);
JsonObject root = json_document.to<JsonObject>();
f(root);
json_document.shrinkToFit();
// The Json buffer size gives us a good estimate for the required size.
// Usually, it's a bit larger than the actual required string size
// | JSON Buffer Size | String Size |
// Discovery | 388 | 351 |
// Discovery | 372 | 356 |
// Discovery | 336 | 311 |
// Discovery | 408 | 393 |
global_json_build_buffer.reserve(global_json_buffer.size() + 1);
size_t bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity());
if (bytes_written >= global_json_build_buffer.capacity() - 1) {
global_json_build_buffer.reserve(root.measureLength() + 1);
bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity());
}
*length = bytes_written;
return global_json_build_buffer.data();
std::string output;
serializeJson(json_document, output);
return output;
}
void parse_json(const std::string &data, const json_parse_t &f) {
global_json_buffer.clear();
JsonObject &root = global_json_buffer.parseObject(data);
if (!root.success()) {
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
// 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)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048;
#endif
DynamicJsonDocument json_document(free_heap);
DeserializationError err = deserializeJson(json_document, data);
json_document.shrinkToFit();
JsonObject root = json_document.as<JsonObject>();
if (err) {
ESP_LOGW(TAG, "Parsing JSON failed.");
return;
}
f(root);
}
std::string build_json(const json_build_t &f) {
size_t len;
const char *c_str = build_json(f, &len);
return std::string(c_str, len);
}
VectorJsonBuffer::String::String(VectorJsonBuffer *parent) : parent_(parent), start_(parent->size_) {}
void VectorJsonBuffer::String::append(char c) const {
char *last = static_cast<char *>(this->parent_->do_alloc(1));
*last = c;
}
const char *VectorJsonBuffer::String::c_str() const {
this->append('\0');
return &this->parent_->buffer_[this->start_];
}
void VectorJsonBuffer::clear() {
for (char *block : this->free_blocks_)
free(block); // NOLINT
this->size_ = 0;
this->free_blocks_.clear();
}
VectorJsonBuffer::String VectorJsonBuffer::startString() { return {this}; } // NOLINT
void *VectorJsonBuffer::alloc(size_t bytes) {
// Make sure memory addresses are aligned
uint32_t new_size = round_size_up(this->size_);
this->resize(new_size);
return this->do_alloc(bytes);
}
void *VectorJsonBuffer::do_alloc(size_t bytes) { // NOLINT
const uint32_t begin = this->size_;
this->resize(begin + bytes);
return &this->buffer_[begin];
}
void VectorJsonBuffer::resize(size_t size) { // NOLINT
if (size <= this->size_) {
this->size_ = size;
return;
}
this->reserve(size);
this->size_ = size;
}
void VectorJsonBuffer::reserve(size_t size) { // NOLINT
if (size <= this->capacity_)
return;
uint32_t target_capacity = this->capacity_;
if (this->capacity_ == 0) {
// lazily initialize with a reasonable size
target_capacity = JSON_OBJECT_SIZE(16);
}
while (target_capacity < size)
target_capacity *= 2;
char *old_buffer = this->buffer_;
this->buffer_ = new char[target_capacity]; // NOLINT
if (old_buffer != nullptr && this->capacity_ != 0) {
this->free_blocks_.push_back(old_buffer);
memcpy(this->buffer_, old_buffer, this->capacity_);
}
this->capacity_ = target_capacity;
}
size_t VectorJsonBuffer::size() const { return this->size_; }
VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace json
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,68 +1,28 @@
#pragma once
#ifdef USE_ARDUINO
#include <vector>
#include "esphome/core/helpers.h"
#undef ARDUINOJSON_ENABLE_STD_STRING
#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT
#include <ArduinoJson.h>
namespace esphome {
namespace json {
/// Callback function typedef for parsing JsonObjects.
using json_parse_t = std::function<void(JsonObject &)>;
using json_parse_t = std::function<void(JsonObject)>;
/// Callback function typedef for building JsonObjects.
using json_build_t = std::function<void(JsonObject &)>;
using json_build_t = std::function<void(JsonObject)>;
/// Build a JSON string with the provided json build function.
const char *build_json(const json_build_t &f, size_t *length);
std::string build_json(const json_build_t &f);
/// Parse a JSON string and run the provided json parse function if it's valid.
void parse_json(const std::string &data, const json_parse_t &f);
class VectorJsonBuffer : public ArduinoJson::Internals::JsonBufferBase<VectorJsonBuffer> {
public:
class String {
public:
String(VectorJsonBuffer *parent);
void append(char c) const;
const char *c_str() const;
protected:
VectorJsonBuffer *parent_;
uint32_t start_;
};
void *alloc(size_t bytes) override;
size_t size() const;
void clear();
String startString(); // NOLINT
protected:
void *do_alloc(size_t bytes); // NOLINT
void resize(size_t size); // NOLINT
void reserve(size_t size); // NOLINT
char *buffer_{nullptr};
size_t size_{0};
size_t capacity_{0};
std::vector<char *> free_blocks_;
};
extern VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace json
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@Cat-Ion"]

View File

@@ -0,0 +1,82 @@
#include "kalman_combinator.h"
#include "esphome/core/hal.h"
#include <cmath>
#include <functional>
namespace esphome {
namespace kalman_combinator {
void KalmanCombinatorComponent::dump_config() {
ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:");
ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_);
ESP_LOGCONFIG("kalman_combinator", " Sensors:");
for (const auto &sensor : this->sensors_) {
auto &entity = *sensor.first;
ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str());
}
}
void KalmanCombinatorComponent::setup() {
for (const auto &sensor : this->sensors_) {
const auto stddev = sensor.second;
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
}
}
void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
this->sensors_.emplace_back(sensor, stddev);
}
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float) -> float { return stddev; }});
}
void KalmanCombinatorComponent::update_variance_() {
uint32_t now = millis();
// Variance increases by update_variance_ each millisecond
auto dt = now - this->last_update_;
auto dv = this->update_variance_value_ * dt;
this->variance_ += dv;
this->last_update_ = now;
}
void KalmanCombinatorComponent::correct_(float value, float stddev) {
if (std::isnan(value) || std::isinf(stddev)) {
return;
}
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
this->state_ = value;
this->variance_ = stddev * stddev;
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(stddev);
}
return;
}
this->update_variance_();
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
// Use the value with the smaller variance as mu1 to prevent precision errors
const bool this_first = this->variance_ < (stddev * stddev);
const float mu1 = this_first ? this->state_ : value;
const float mu2 = this_first ? value : this->state_;
const float var1 = this_first ? this->variance_ : stddev * stddev;
const float var2 = this_first ? stddev * stddev : this->variance_;
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
const float var = var1 - (var1 * var1) / (var1 + var2);
// Update and publish state
this->state_ = mu;
this->variance_ = var;
this->publish_state(mu);
if (this->std_dev_sensor_ != nullptr) {
this->std_dev_sensor_->publish_state(std::sqrt(var));
}
}
} // namespace kalman_combinator
} // namespace esphome

View File

@@ -0,0 +1,46 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include <cmath>
#include <vector>
namespace esphome {
namespace kalman_combinator {
class KalmanCombinatorComponent : public Component, public sensor::Sensor {
public:
KalmanCombinatorComponent() = default;
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void dump_config() override;
void setup() override;
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
void add_source(Sensor *sensor, float stddev);
void set_process_std_dev(float process_std_dev) {
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
}
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
private:
void update_variance_();
void correct_(float value, float stddev);
// Source sensors and their error functions
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensors_;
// Optional sensor for publishing the current error
sensor::Sensor *std_dev_sensor_{nullptr};
// Tick of the last update
uint32_t last_update_{0};
// Change of the variance, per ms
float update_variance_value_{0.f};
// Best guess for the state and its variance
float state_{NAN};
float variance_{INFINITY};
};
} // namespace kalman_combinator
} // namespace esphome

View File

@@ -0,0 +1,87 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
CONF_SOURCE,
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_UNIT_OF_MEASUREMENT,
)
from esphome.core.entity_helpers import inherit_property_from
kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator")
KalmanCombinatorComponent = kalman_combinator_ns.class_(
"KalmanCombinatorComponent", cg.Component, sensor.Sensor
)
CONF_ERROR = "error"
CONF_SOURCES = "sources"
CONF_PROCESS_STD_DEV = "process_std_dev"
CONF_STD_DEV = "std_dev"
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent),
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
cv.Required(CONF_SOURCES): cv.ensure_list(
cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
}
),
),
cv.Optional(CONF_STD_DEV): sensor.SENSOR_SCHEMA,
}
)
# Inherit some sensor values from the first source, for both the state and the error value
properties_to_inherit = [
CONF_ACCURACY_DECIMALS,
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_UNIT_OF_MEASUREMENT,
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
]
inherit_schema_for_state = [
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
inherit_schema_for_std_dev = [
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
for property in properties_to_inherit
]
FINAL_VALIDATE_SCHEMA = cv.All(
CONFIG_SCHEMA.extend(
{cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)},
extra=cv.ALLOW_EXTRA,
),
*inherit_schema_for_state,
*inherit_schema_for_std_dev,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV]))
for source_conf in config[CONF_SOURCES]:
source = await cg.get_variable(source_conf[CONF_SOURCE])
error = await cg.templatable(
source_conf[CONF_ERROR],
[(float, "x")],
cg.float_,
)
cg.add(var.add_source(source, error))
if CONF_STD_DEV in config:
sens = await sensor.new_sensor(config[CONF_STD_DEV])
cg.add(var.set_std_dev_sensor(sens))

View File

@@ -331,9 +331,10 @@ class AddressableFlickerEffect : public AddressableLightEffect {
return;
this->last_update_ = now;
fast_random_set_seed(random_uint32());
uint32_t rng_state = random_uint32();
for (auto var : it) {
const uint8_t flicker = fast_random_8() % intensity;
rng_state = (rng_state * 0x9E3779B9) + 0x9E37;
const uint8_t flicker = (rng_state & 0xFF) % intensity;
// scale down by random factor
var = var.get() * (255 - flicker);

View File

@@ -8,7 +8,7 @@ namespace light {
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
if (state.supports_effects())
root["effect"] = state.get_effect_name();
@@ -52,7 +52,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
root["brightness"] = uint8_t(values.get_brightness() * 255);
JsonObject &color = root.createNestedObject("color");
JsonObject color = root.createNestedObject("color");
if (values.get_color_mode() & ColorCapability::RGB) {
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
@@ -72,7 +72,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
}
}
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject &root) {
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) {
if (root.containsKey("state")) {
auto val = parse_on_off(root["state"]);
switch (val) {
@@ -95,7 +95,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
}
if (root.containsKey("color")) {
JsonObject &color = root["color"];
JsonObject color = root["color"];
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
float max_rgb = 0.0f;
if (color.containsKey("r")) {
@@ -140,7 +140,7 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
}
}
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject &root) {
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) {
LightJSONSchema::parse_color_json(state, call, root);
if (root.containsKey("flash")) {

View File

@@ -14,12 +14,12 @@ namespace light {
class LightJSONSchema {
public:
/// Dump the state of a light as JSON.
static void dump_json(LightState &state, JsonObject &root);
static void dump_json(LightState &state, JsonObject root);
/// Parse the JSON state of a light to a LightCall.
static void parse_json(LightState &state, LightCall &call, JsonObject &root);
static void parse_json(LightState &state, LightCall &call, JsonObject root);
protected:
static void parse_color_json(LightState &state, LightCall &call, JsonObject &root);
static void parse_color_json(LightState &state, LightCall &call, JsonObject root);
};
} // namespace light

View File

@@ -0,0 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi
from esphome.const import CONF_ID
DEPENDENCIES = ["spi"]
MULTI_CONF = True
CODEOWNERS = ["@rsumner"]
mcp3204_ns = cg.esphome_ns.namespace("mcp3204")
MCP3204 = mcp3204_ns.class_("MCP3204", cg.Component, spi.SPIDevice)
CONF_REFERENCE_VOLTAGE = "reference_voltage"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(MCP3204),
cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage,
}
).extend(spi.spi_device_schema(cs_pin_required=True))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
await cg.register_component(var, config)
await spi.register_spi_device(var, config)

View File

@@ -0,0 +1,35 @@
#include "mcp3204.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp3204 {
static const char *const TAG = "mcp3204";
float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; }
void MCP3204::setup() {
ESP_LOGCONFIG(TAG, "Setting up mcp3204");
this->spi_setup();
}
void MCP3204::dump_config() {
ESP_LOGCONFIG(TAG, "MCP3204:");
LOG_PIN(" CS Pin:", this->cs_);
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
}
float MCP3204::read_data(uint8_t pin) {
uint8_t adc_primary_config = 0b00000110 & 0b00000111;
uint8_t adc_secondary_config = pin << 6;
this->enable();
this->transfer_byte(adc_primary_config);
uint8_t adc_primary_byte = this->transfer_byte(adc_secondary_config);
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
this->disable();
uint16_t digital_value = (adc_primary_byte << 8 | adc_secondary_byte) & 0b111111111111;
return float(digital_value) / 4096.000 * this->reference_voltage_;
}
} // namespace mcp3204
} // namespace esphome

View File

@@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace mcp3204 {
class MCP3204 : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> {
public:
MCP3204() = default;
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
float read_data(uint8_t pin);
protected:
float reference_voltage_;
};
} // namespace mcp3204
} // namespace esphome

View File

@@ -0,0 +1,32 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_ID, CONF_NUMBER
from .. import mcp3204_ns, MCP3204
AUTO_LOAD = ["voltage_sampler"]
DEPENDENCIES = ["mcp3204"]
MCP3204Sensor = mcp3204_ns.class_(
"MCP3204Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
CONF_MCP3204_ID = "mcp3204_id"
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),
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_NUMBER],
)
await cg.register_parented(var, config[CONF_MCP3204_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)

View File

@@ -0,0 +1,23 @@
#include "mcp3204_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp3204 {
static const char *const TAG = "mcp3204.sensor";
MCP3204Sensor::MCP3204Sensor(uint8_t pin) : pin_(pin) {}
float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; }
void MCP3204Sensor::dump_config() {
LOG_SENSOR("", "MCP3204 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
LOG_UPDATE_INTERVAL(this);
}
float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_); }
void MCP3204Sensor::update() { this->publish_state(this->sample()); }
} // namespace mcp3204
} // namespace esphome

View File

@@ -0,0 +1,30 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "../mcp3204.h"
namespace esphome {
namespace mcp3204 {
class MCP3204Sensor : public PollingComponent,
public Parented<MCP3204>,
public sensor::Sensor,
public voltage_sampler::VoltageSampler {
public:
MCP3204Sensor(uint8_t pin);
void update() override;
void dump_config() override;
float get_setup_priority() const override;
float sample() override;
protected:
uint8_t pin_;
};
} // namespace mcp3204
} // namespace esphome

View File

View File

@@ -0,0 +1,21 @@
#include "mcp47a1.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp47a1 {
static const char *const TAG = "mcp47a1";
void MCP47A1::dump_config() {
ESP_LOGCONFIG(TAG, "MCP47A1 Output:");
LOG_I2C_DEVICE(this);
}
void MCP47A1::write_state(float state) {
const uint8_t value = remap(state, 0.0f, 1.0f, 63, 0);
this->write_byte(0, value);
}
} // namespace mcp47a1
} // namespace esphome

View File

@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/output/float_output.h"
#include "esphome/core/component.h"
namespace esphome {
namespace mcp47a1 {
class MCP47A1 : public Component, public output::FloatOutput, public i2c::I2CDevice {
public:
void dump_config() override;
void write_state(float state) override;
};
} // namespace mcp47a1
} // namespace esphome

View File

@@ -0,0 +1,27 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import output, i2c
from esphome.const import CONF_ID
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
mcp47a1_ns = cg.esphome_ns.namespace("mcp47a1")
MCP47A1 = mcp47a1_ns.class_("MCP47A1", output.FloatOutput, cg.Component, i2c.I2CDevice)
CONFIG_SCHEMA = (
output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(MCP47A1),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x2E))
)
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)
await output.register_output(var, config)

View File

@@ -23,12 +23,12 @@ class IrFollowMeData : public IrData {
}
/* TEMPERATURE */
uint8_t temp() const { return this->data_[4] - 1; }
void set_temp(uint8_t val) { this->data_[4] = std::min(MAX_TEMP, val) + 1; }
uint8_t temp() const { return this->get_value_(4) - 1; }
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
/* BEEPER */
bool beeper() const { return this->data_[3] & 128; }
void set_beeper(bool val) { this->set_value_(3, 1, 7, val); }
bool beeper() const { return this->get_value_(3, 128); }
void set_beeper(bool val) { this->set_mask_(3, val, 128); }
protected:
static const uint8_t MAX_TEMP = 37;

View File

View File

@@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir", "coolix"]
CODEOWNERS = ["@dudanov"]
midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
CONF_USE_FAHRENHEIT = "use_fahrenheit"
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MideaIR),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))

View File

@@ -0,0 +1,92 @@
#pragma once
#include "esphome/components/remote_base/midea_protocol.h"
#include "esphome/components/climate/climate_mode.h"
namespace esphome {
namespace midea_ir {
using climate::ClimateMode;
using climate::ClimateFanMode;
using remote_base::MideaData;
class ControlData : public MideaData {
public:
// Default constructor (power: ON, mode: AUTO, fan: AUTO, temp: 25C)
ControlData() : MideaData({MIDEA_TYPE_CONTROL, 0x82, 0x48, 0xFF, 0xFF}) {}
// Copy from Base
ControlData(const MideaData &data) : MideaData(data) {}
void set_temp(float temp);
float get_temp() const;
void set_mode(ClimateMode mode);
ClimateMode get_mode() const;
void set_fan_mode(ClimateFanMode mode);
ClimateFanMode get_fan_mode() const;
void set_sleep_preset(bool value) { this->set_mask_(1, value, 64); }
bool get_sleep_preset() const { return this->get_value_(1, 64); }
void set_fahrenheit(bool value) { this->set_mask_(2, value, 32); }
bool get_fahrenheit() const { return this->get_value_(2, 32); }
void fix();
protected:
enum Mode : uint8_t {
MODE_COOL,
MODE_DRY,
MODE_AUTO,
MODE_HEAT,
MODE_FAN_ONLY,
};
enum FanMode : uint8_t {
FAN_AUTO,
FAN_LOW,
FAN_MEDIUM,
FAN_HIGH,
};
void set_fan_mode_(FanMode mode) { this->set_value_(1, mode, 3, 3); }
FanMode get_fan_mode_() const { return static_cast<FanMode>(this->get_value_(1, 3, 3)); }
void set_mode_(Mode mode) { this->set_value_(1, mode, 7); }
Mode get_mode_() const { return static_cast<Mode>(this->get_value_(1, 7)); }
void set_power_(bool value) { this->set_mask_(1, value, 128); }
bool get_power_() const { return this->get_value_(1, 128); }
};
class FollowMeData : public MideaData {
public:
// Default constructor (temp: 30C, beeper: off)
FollowMeData() : MideaData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
// Copy from Base
FollowMeData(const MideaData &data) : MideaData(data) {}
// Direct from temperature and beeper values
FollowMeData(uint8_t temp, bool beeper = false) : FollowMeData() {
this->set_temp(temp);
this->set_beeper(beeper);
}
/* TEMPERATURE */
uint8_t temp() const { return this->get_value_(4) - 1; }
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
/* BEEPER */
bool beeper() const { return this->get_value_(3, 128); }
void set_beeper(bool value) { this->set_mask_(3, value, 128); }
protected:
static const uint8_t MAX_TEMP = 37;
};
class SpecialData : public MideaData {
public:
SpecialData(uint8_t code) : MideaData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
static const uint8_t VSWING_STEP = 1;
static const uint8_t VSWING_TOGGLE = 2;
static const uint8_t TURBO_TOGGLE = 9;
};
} // namespace midea_ir
} // namespace esphome

View File

@@ -0,0 +1,201 @@
#include "midea_ir.h"
#include "midea_data.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/components/coolix/coolix.h"
namespace esphome {
namespace midea_ir {
static const char *const TAG = "midea_ir.climate";
void ControlData::set_temp(float temp) {
uint8_t min;
if (this->get_fahrenheit()) {
min = MIDEA_TEMPF_MIN;
temp = esphome::clamp<float>(celsius_to_fahrenheit(temp), MIDEA_TEMPF_MIN, MIDEA_TEMPF_MAX);
} else {
min = MIDEA_TEMPC_MIN;
temp = esphome::clamp<float>(temp, MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX);
}
this->set_value_(2, lroundf(temp) - min, 31);
}
float ControlData::get_temp() const {
const uint8_t temp = this->get_value_(2, 31);
if (this->get_fahrenheit())
return fahrenheit_to_celsius(static_cast<float>(temp + MIDEA_TEMPF_MIN));
return static_cast<float>(temp + MIDEA_TEMPC_MIN);
}
void ControlData::fix() {
// In FAN_AUTO, modes COOL, HEAT and FAN_ONLY bit #5 in byte #1 must be set
const uint8_t value = this->get_value_(1, 31);
if (value == 0 || value == 3 || value == 4)
this->set_mask_(1, true, 32);
// In FAN_ONLY mode we need to set all temperature bits
if (this->get_mode_() == MODE_FAN_ONLY)
this->set_mask_(2, true, 31);
}
void ControlData::set_mode(ClimateMode mode) {
switch (mode) {
case ClimateMode::CLIMATE_MODE_OFF:
this->set_power_(false);
return;
case ClimateMode::CLIMATE_MODE_COOL:
this->set_mode_(MODE_COOL);
break;
case ClimateMode::CLIMATE_MODE_DRY:
this->set_mode_(MODE_DRY);
break;
case ClimateMode::CLIMATE_MODE_FAN_ONLY:
this->set_mode_(MODE_FAN_ONLY);
break;
case ClimateMode::CLIMATE_MODE_HEAT:
this->set_mode_(MODE_HEAT);
break;
default:
this->set_mode_(MODE_AUTO);
break;
}
this->set_power_(true);
}
ClimateMode ControlData::get_mode() const {
if (!this->get_power_())
return ClimateMode::CLIMATE_MODE_OFF;
switch (this->get_mode_()) {
case MODE_COOL:
return ClimateMode::CLIMATE_MODE_COOL;
case MODE_DRY:
return ClimateMode::CLIMATE_MODE_DRY;
case MODE_FAN_ONLY:
return ClimateMode::CLIMATE_MODE_FAN_ONLY;
case MODE_HEAT:
return ClimateMode::CLIMATE_MODE_HEAT;
default:
return ClimateMode::CLIMATE_MODE_HEAT_COOL;
}
}
void ControlData::set_fan_mode(ClimateFanMode mode) {
switch (mode) {
case ClimateFanMode::CLIMATE_FAN_LOW:
this->set_fan_mode_(FAN_LOW);
break;
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
this->set_fan_mode_(FAN_MEDIUM);
break;
case ClimateFanMode::CLIMATE_FAN_HIGH:
this->set_fan_mode_(FAN_HIGH);
break;
default:
this->set_fan_mode_(FAN_AUTO);
break;
}
}
ClimateFanMode ControlData::get_fan_mode() const {
switch (this->get_fan_mode_()) {
case FAN_LOW:
return ClimateFanMode::CLIMATE_FAN_LOW;
case FAN_MEDIUM:
return ClimateFanMode::CLIMATE_FAN_MEDIUM;
case FAN_HIGH:
return ClimateFanMode::CLIMATE_FAN_HIGH;
default:
return ClimateFanMode::CLIMATE_FAN_AUTO;
}
}
void MideaIR::control(const climate::ClimateCall &call) {
// swing and preset resets after unit powered off
if (call.get_mode() == climate::CLIMATE_MODE_OFF) {
this->swing_mode = climate::CLIMATE_SWING_OFF;
this->preset = climate::CLIMATE_PRESET_NONE;
} else if (call.get_swing_mode().has_value() && ((*call.get_swing_mode() == climate::CLIMATE_SWING_OFF &&
this->swing_mode == climate::CLIMATE_SWING_VERTICAL) ||
(*call.get_swing_mode() == climate::CLIMATE_SWING_VERTICAL &&
this->swing_mode == climate::CLIMATE_SWING_OFF))) {
this->swing_ = true;
} else if (call.get_preset().has_value() &&
((*call.get_preset() == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) ||
(*call.get_preset() == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) {
this->boost_ = true;
}
climate_ir::ClimateIR::control(call);
}
void MideaIR::transmit_(MideaData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
void MideaIR::transmit_state() {
if (this->swing_) {
SpecialData data(SpecialData::VSWING_TOGGLE);
this->transmit_(data);
this->swing_ = false;
return;
}
if (this->boost_) {
SpecialData data(SpecialData::TURBO_TOGGLE);
this->transmit_(data);
this->boost_ = false;
return;
}
ControlData data;
data.set_fahrenheit(this->fahrenheit_);
data.set_temp(this->target_temperature);
data.set_mode(this->mode);
data.set_fan_mode(this->fan_mode.value_or(ClimateFanMode::CLIMATE_FAN_AUTO));
data.set_sleep_preset(this->preset == climate::CLIMATE_PRESET_SLEEP);
data.fix();
this->transmit_(data);
}
bool MideaIR::on_receive(remote_base::RemoteReceiveData data) {
auto midea = remote_base::MideaProtocol().decode(data);
if (midea.has_value())
return this->on_midea_(*midea);
return coolix::CoolixClimate::on_coolix(this, data);
}
bool MideaIR::on_midea_(const MideaData &data) {
ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str());
if (data.type() == MideaData::MIDEA_TYPE_CONTROL) {
const ControlData status = data;
if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY)
this->target_temperature = status.get_temp();
this->mode = status.get_mode();
this->fan_mode = status.get_fan_mode();
if (status.get_sleep_preset())
this->preset = climate::CLIMATE_PRESET_SLEEP;
else if (this->preset == climate::CLIMATE_PRESET_SLEEP)
this->preset = climate::CLIMATE_PRESET_NONE;
this->publish_state();
return true;
}
if (data.type() == MideaData::MIDEA_TYPE_SPECIAL) {
switch (data[1]) {
case SpecialData::VSWING_TOGGLE:
this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? climate::CLIMATE_SWING_OFF
: climate::CLIMATE_SWING_VERTICAL;
break;
case SpecialData::TURBO_TOGGLE:
this->preset = this->preset == climate::CLIMATE_PRESET_BOOST ? climate::CLIMATE_PRESET_NONE
: climate::CLIMATE_PRESET_BOOST;
break;
}
this->publish_state();
return true;
}
return false;
}
} // namespace midea_ir
} // namespace esphome

View File

@@ -0,0 +1,47 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
#include "midea_data.h"
namespace esphome {
namespace midea_ir {
// Temperature
const uint8_t MIDEA_TEMPC_MIN = 17; // Celsius
const uint8_t MIDEA_TEMPC_MAX = 30; // Celsius
const uint8_t MIDEA_TEMPF_MIN = 62; // Fahrenheit
const uint8_t MIDEA_TEMPF_MAX = 86; // Fahrenheit
class MideaIR : public climate_ir::ClimateIR {
public:
MideaIR()
: climate_ir::ClimateIR(
MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL},
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_SLEEP, climate::CLIMATE_PRESET_BOOST}) {}
/// Override control to change settings of the climate device.
void control(const climate::ClimateCall &call) override;
/// Set use of Fahrenheit units
void set_fahrenheit(bool value) {
this->fahrenheit_ = value;
this->temperature_step_ = value ? 0.5f : 1.0f;
}
protected:
/// Transmit via IR the state of this climate controller.
void transmit_state() override;
void transmit_(MideaData &data);
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool on_midea_(const MideaData &data);
bool fahrenheit_{false};
bool swing_{false};
bool boost_{false};
};
} // namespace midea_ir
} // namespace esphome

View File

@@ -69,7 +69,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
uint8_t data_len = raw[2];
uint8_t data_offset = 3;
// the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands
if (function_code == 0x5 || function_code == 0x06 || function_code == 0x10) {
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
data_offset = 2;
data_len = 4;
}

View File

@@ -47,11 +47,16 @@ MODBUS_FUNCTION_CODE = {
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_REGISTER_TYPE = {
MODBUS_WRITE_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"holding": ModbusRegisterType.HOLDING,
}
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
}

View File

@@ -57,4 +57,4 @@ async def to_code(config):
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
cg.add(paren.add_sensor_item(var))
await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool)
await add_modbus_base_properties(var, config, ModbusBinarySensor, bool, bool)

View File

@@ -31,12 +31,11 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
void dump_config() override;
using transform_func_t =
optional<std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>>;
using transform_func_t = std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>;
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
protected:
transform_func_t transform_func_{nullopt};
optional<transform_func_t> transform_func_{nullopt};
};
} // namespace modbus_controller

View File

@@ -24,15 +24,22 @@ bool ModbusController::send_next_command_() {
if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) {
auto &command = command_queue_.front();
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
command->register_address, command->register_count);
command->send();
this->last_command_timestamp_ = millis();
// remove from queue if no handler is defined or command was sent too often
if (!command->on_data_func || command->send_countdown < 1) {
ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send",
this->address_, command->register_address, command->send_countdown);
// remove from queue if command was sent too often
if (command->send_countdown < 1) {
ESP_LOGD(
TAG,
"Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue",
this->address_, command->register_address, command->send_countdown);
command_queue_.pop_front();
} else {
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
command->register_address, command->register_count);
command->send();
this->last_command_timestamp_ = millis();
// remove from queue if no handler is defined
if (!command->on_data_func) {
command_queue_.pop_front();
}
}
}
return (!command_queue_.empty());
@@ -72,36 +79,28 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
}
}
std::map<uint64_t, SensorItem *>::iterator ModbusController::find_register_(ModbusRegisterType register_type,
uint16_t start_address) {
auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
return (r.start_address == start_address && r.register_type == register_type);
});
if (vec_it == register_ranges_.end()) {
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
if (reg_it == register_ranges_.end()) {
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
} else {
auto map_it = sensormap_.find(vec_it->first_sensorkey);
if (map_it == sensormap_.end()) {
ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey);
} else {
return sensormap_.find(vec_it->first_sensorkey);
}
return reg_it->sensors;
}
// not found
return std::end(sensormap_);
return {};
}
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
auto map_it = find_register_(register_type, start_address);
// loop through all sensors with the same start address
while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
if (map_it->second->register_type == register_type) {
map_it->second->parse_and_publish(data);
}
map_it++;
auto sensors = find_sensors_(register_type, start_address);
for (auto sensor : sensors) {
sensor->parse_and_publish(data);
}
}
@@ -110,7 +109,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
// not very effective but the queue is never really large
for (auto &item : command_queue_) {
if (item->register_address == command.register_address && item->register_count == command.register_count &&
item->register_type == command.register_type) {
item->register_type == command.register_type && item->function_code == command.function_code) {
ESP_LOGW(TAG, "Duplicate modbus command found");
// update the payload of the queued command
// replaces a previous command
@@ -127,15 +126,16 @@ void ModbusController::update_range_(RegisterRange &r) {
if (r.skip_updates_counter == 0) {
// if a custom command is used the user supplied custom_data is only available in the SensorItem.
if (r.register_type == ModbusRegisterType::CUSTOM) {
auto it = this->find_register_(r.register_type, r.start_address);
if (it != sensormap_.end()) {
auto sensors = this->find_sensors_(r.register_type, r.start_address);
if (!sensors.empty()) {
auto sensor = sensors.cbegin();
auto command_item = ModbusCommandItem::create_custom_command(
this, it->second->custom_data,
this, (*sensor)->custom_data,
[this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
});
command_item.register_address = it->second->start_address;
command_item.register_count = it->second->register_count;
command_item.register_address = (*sensor)->start_address;
command_item.register_count = (*sensor)->register_count;
command_item.function_code = ModbusFunctionCode::CUSTOM;
queue_command(command_item);
}
@@ -164,102 +164,110 @@ void ModbusController::update() {
}
}
// walk through the sensors and determine the registerranges to read
// walk through the sensors and determine the register ranges to read
size_t ModbusController::create_register_ranges_() {
register_ranges_.clear();
uint8_t n = 0;
if (sensormap_.empty()) {
if (sensorset_.empty()) {
ESP_LOGW(TAG, "No sensors registered");
return 0;
}
auto ix = sensormap_.begin();
auto prev = ix;
int total_register_count = 0;
uint16_t current_start_address = ix->second->start_address;
uint8_t buffer_offset = ix->second->offset;
uint8_t skip_updates = ix->second->skip_updates;
auto first_sensorkey = ix->second->getkey();
total_register_count = 0;
while (ix != sensormap_.end()) {
ESP_LOGV(TAG, "Register: 0x%X %d %d 0x%llx (%d) buffer_offset = %d (0x%X) skip=%u", ix->second->start_address,
ix->second->register_count, ix->second->offset, ix->second->getkey(), total_register_count, buffer_offset,
buffer_offset, ix->second->skip_updates);
// if this is a sequential address based on number of registers and address of previous sensor
// convert to an offset to the previous sensor (address 0x101 becomes address 0x100 offset 2 bytes)
if (!ix->second->force_new_range && total_register_count >= 0 &&
prev->second->register_type == ix->second->register_type &&
prev->second->start_address + total_register_count == ix->second->start_address &&
prev->second->start_address < ix->second->start_address) {
ix->second->start_address = prev->second->start_address;
ix->second->offset += prev->second->offset + prev->second->get_register_size();
// iterator is sorted see SensorItemsComparator for details
auto ix = sensorset_.begin();
RegisterRange r = {};
uint8_t buffer_offset = 0;
SensorItem *prev = nullptr;
while (ix != sensorset_.end()) {
SensorItem *curr = *ix;
// replace entry in sensormap_
auto const value = ix->second;
sensormap_.erase(ix);
sensormap_.insert({value->getkey(), value});
// move iterator back to new element
ix = sensormap_.find(value->getkey()); // next(prev, 1);
}
if (current_start_address != ix->second->start_address ||
// ( prev->second->start_address + prev->second->offset != ix->second->start_address) ||
ix->second->register_type != prev->second->register_type) {
// Difference doesn't match so we have a gap
if (n > 0) {
RegisterRange r;
r.start_address = current_start_address;
r.register_count = total_register_count;
if (prev->second->register_type == ModbusRegisterType::COIL ||
prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) {
r.register_count = prev->second->offset + 1;
}
r.register_type = prev->second->register_type;
r.first_sensorkey = first_sensorkey;
r.skip_updates = skip_updates;
r.skip_updates_counter = 0;
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
}
skip_updates = ix->second->skip_updates;
current_start_address = ix->second->start_address;
first_sensorkey = ix->second->getkey();
total_register_count = ix->second->register_count;
buffer_offset = ix->second->offset;
n = 1;
ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
curr->offset, curr->get_register_size(), curr->offset, curr->skip_updates, curr);
if (r.register_count == 0) {
// this is the first register in range
r.start_address = curr->start_address;
r.register_count = curr->register_count;
r.register_type = curr->register_type;
r.sensors.insert(curr);
r.skip_updates = curr->skip_updates;
r.skip_updates_counter = 0;
buffer_offset = curr->get_register_size();
ESP_LOGV(TAG, "Started new range");
} else {
n++;
if (ix->second->offset != prev->second->offset || n == 1) {
total_register_count += ix->second->register_count;
buffer_offset += ix->second->get_register_size();
// this is not the first register in range so it might be possible
// to reuse the last register or extend the current range
if (!curr->force_new_range && r.register_type == curr->register_type &&
curr->register_type != ModbusRegisterType::CUSTOM) {
if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
// this register can re-use the data from the previous register
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += prev->offset;
sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
ESP_LOGV(TAG, "Re-use previous register - change to register: 0x%X %d offset=%u", curr->start_address,
curr->register_count, curr->offset);
} else if (curr->start_address == (r.start_address + r.register_count)) {
// this register can extend the current range
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += buffer_offset;
buffer_offset += curr->get_register_size();
r.register_count += curr->register_count;
sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
ESP_LOGV(TAG, "Extend range - change to register: 0x%X %d offset=%u", curr->start_address,
curr->register_count, curr->offset);
}
}
}
if (curr->start_address == r.start_address) {
// use the lowest non zero value for the whole range
// Because zero is the default value for skip_updates it is excluded from getting the min value.
if (ix->second->skip_updates != 0) {
if (skip_updates != 0) {
skip_updates = std::min(skip_updates, ix->second->skip_updates);
if (curr->skip_updates != 0) {
if (r.skip_updates != 0) {
r.skip_updates = std::min(r.skip_updates, curr->skip_updates);
} else {
skip_updates = ix->second->skip_updates;
r.skip_updates = curr->skip_updates;
}
}
// add sensor to this range
r.sensors.insert(curr);
ix++;
} else {
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
r = {};
buffer_offset = 0;
// do not increment the iterator here because the current sensor has to be re-evaluated
}
prev = ix++;
prev = curr;
}
// Add the last range
if (n > 0) {
RegisterRange r;
r.start_address = current_start_address;
// r.register_count = prev->second->offset>>1 + prev->second->get_register_size();
r.register_count = total_register_count;
if (prev->second->register_type == ModbusRegisterType::COIL ||
prev->second->register_type == ModbusRegisterType::DISCRETE_INPUT) {
r.register_count = prev->second->offset + 1;
}
r.register_type = prev->second->register_type;
r.first_sensorkey = first_sensorkey;
r.skip_updates = skip_updates;
r.skip_updates_counter = 0;
if (r.register_count > 0) {
// Add the last range
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
}
return register_ranges_.size();
}
@@ -268,9 +276,15 @@ void ModbusController::dump_config() {
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGCONFIG(TAG, "sensormap");
for (auto &it : sensormap_) {
ESP_LOGCONFIG("TAG", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(), it.second->start_address,
it.second->register_count, it.second->get_register_size());
for (auto &it : sensorset_) {
ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
it->get_register_size());
}
ESP_LOGCONFIG(TAG, "ranges");
for (auto &it : register_ranges_) {
ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
it.start_address, it.register_count, it.skip_updates);
}
#endif
}
@@ -294,11 +308,11 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty
ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data<uint16_t>(data, 0), get_data<int16_t>(data, 1));
}
void ModbusController::dump_sensormap_() {
ESP_LOGV("modbuscontroller.h", "sensormap");
for (auto &it : sensormap_) {
ESP_LOGV("modbuscontroller.h", " Sensor 0x%llX start=0x%X count=%d size=%d", it.second->getkey(),
it.second->start_address, it.second->register_count, it.second->get_register_size());
void ModbusController::dump_sensors_() {
ESP_LOGV(TAG, "sensors");
for (auto &it : sensorset_) {
ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
it->get_register_size(), it->offset);
}
}

View File

@@ -6,7 +6,7 @@
#include "esphome/components/modbus/modbus.h"
#include <list>
#include <map>
#include <set>
#include <queue>
#include <vector>
@@ -37,7 +37,7 @@ enum class ModbusFunctionCode {
READ_FIFO_QUEUE = 0x18, // not implemented
};
enum class ModbusRegisterType : int {
enum class ModbusRegisterType : uint8_t {
CUSTOM = 0x0,
COIL = 0x01,
DISCRETE_INPUT = 0x02,
@@ -62,15 +62,6 @@ enum class SensorValueType : uint8_t {
FP32_R = 0xD
};
struct RegisterRange {
uint16_t start_address;
ModbusRegisterType register_type;
uint8_t register_count;
uint8_t skip_updates; // the config value
uint64_t first_sensorkey;
uint8_t skip_updates_counter; // the running value
} __attribute__((packed));
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
@@ -108,18 +99,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_
}
}
/** All sensors are stored in a map
* to enable binary sensors for values encoded as bits in the same register the key of each sensor
* the key is a 64 bit integer that combines the register properties
* sensormap_ is sorted by this key. The key ensures the correct order when creating consequtive ranges
* Format: function_code (8 bit) | start address (16 bit)| offset (8bit)| bitmask (32 bit)
*/
inline uint64_t calc_key(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset = 0,
uint32_t bitmask = 0) {
return uint64_t((uint16_t(register_type) << 24) + (uint32_t(start_address) << 8) + (offset & 0xFF)) << 32 | bitmask;
}
inline uint16_t register_from_key(uint64_t key) { return (key >> 40) & 0xFFFF; }
inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); }
/** Get a byte from a hex string
@@ -250,7 +229,6 @@ class SensorItem {
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
size_t virtual get_register_size() const {
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
return 1;
@@ -271,6 +249,48 @@ class SensorItem {
bool force_new_range{false};
};
// ModbusController::create_register_ranges_ tries to optimize register range
// for this the sensors must be ordered by register_type, start_address and bitmask
class SensorItemsComparator {
public:
bool operator()(const SensorItem *lhs, const SensorItem *rhs) const {
// first sort according to register type
if (lhs->register_type != rhs->register_type) {
return lhs->register_type < rhs->register_type;
}
// ensure that sensor with force_new_range set are before the others
if (lhs->force_new_range != rhs->force_new_range) {
return lhs->force_new_range > rhs->force_new_range;
}
// sort by start address
if (lhs->start_address != rhs->start_address) {
return lhs->start_address < rhs->start_address;
}
// sort by offset (ensures update of sensors in ascending order)
if (lhs->offset != rhs->offset) {
return lhs->offset < rhs->offset;
}
// The pointer to the sensor is used last to ensure that
// multiple sensors with the same values can be added with a stable sort order.
return lhs < rhs;
}
};
using SensorSet = std::set<SensorItem *, SensorItemsComparator>;
struct RegisterRange {
uint16_t start_address;
ModbusRegisterType register_type;
uint8_t register_count;
uint8_t skip_updates; // the config value
SensorSet sensors; // all sensors of this range
uint8_t skip_updates_counter; // the running value
};
class ModbusCommandItem {
public:
static const size_t MAX_PAYLOAD_BYTES = 240;
@@ -382,8 +402,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// queues a modbus command in the send queue
void queue_command(const ModbusCommandItem &command);
/// Registers a sensor with the controller. Called by esphomes code generator
void add_sensor_item(SensorItem *item) { sensormap_[item->getkey()] = item; }
/// called when a modbus response was prased without errors
void add_sensor_item(SensorItem *item) { sensorset_.insert(item); }
/// called when a modbus response was parsed without errors
void on_modbus_data(const std::vector<uint8_t> &data) override;
/// called when a modbus error response was received
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
@@ -400,7 +420,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// parse sensormap_ and create range of sequential addresses
size_t create_register_ranges_();
// find register in sensormap. Returns iterator with all registers having the same start address
std::map<uint64_t, SensorItem *>::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address);
SensorSet find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const;
/// submit the read command for the address range to the send queue
void update_range_(RegisterRange &r);
/// parse incoming modbus data
@@ -410,10 +430,9 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// get the number of queued modbus commands (should be mostly empty)
size_t get_command_queue_length_() { return command_queue_.size(); }
/// dump the parsed sensormap for diagnostics
void dump_sensormap_();
void dump_sensors_();
/// Collection of all sensors for this component
/// see calc_key how the key is contructed
std::map<uint64_t, SensorItem *> sensormap_;
SensorSet sensorset_;
/// Continous range of modbus registers
std::vector<RegisterRange> register_ranges_;
/// Hold the pending requests to be sent

View File

@@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output
from esphome.const import (
CONF_ADDRESS,
CONF_ID,
@@ -11,13 +10,14 @@ from esphome.const import (
from .. import (
modbus_controller_ns,
modbus_calc_properties,
validate_modbus_register,
ModbusItemBaseSchema,
SensorItem,
SENSOR_VALUE_TYPE,
)
from ..const import (
CONF_MODBUS_CONTROLLER_ID,
CONF_REGISTER_TYPE,
CONF_USE_WRITE_MULTIPLE,
CONF_VALUE_TYPE,
CONF_WRITE_LAMBDA,
@@ -27,45 +27,83 @@ DEPENDENCIES = ["modbus_controller"]
CODEOWNERS = ["@martgras"]
ModbusOutput = modbus_controller_ns.class_(
"ModbusOutput", cg.Component, output.FloatOutput, SensorItem
ModbusFloatOutput = modbus_controller_ns.class_(
"ModbusFloatOutput", cg.Component, output.FloatOutput, SensorItem
)
ModbusBinaryOutput = modbus_controller_ns.class_(
"ModbusBinaryOutput", cg.Component, output.BinaryOutput, SensorItem
)
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
{
cv.GenerateID(): cv.declare_id(ModbusOutput),
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
}
),
validate_modbus_register,
CONFIG_SCHEMA = cv.typed_schema(
{
"coil": output.BINARY_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
{
cv.GenerateID(): cv.declare_id(ModbusBinaryOutput),
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
}
),
"holding": output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
{
cv.GenerateID(): cv.declare_id(ModbusFloatOutput),
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
SENSOR_VALUE_TYPE
),
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
}
),
},
lower=True,
key=CONF_REGISTER_TYPE,
default_type="holding",
)
async def to_code(config):
byte_offset, reg_count = modbus_calc_properties(config)
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_ADDRESS],
byte_offset,
config[CONF_VALUE_TYPE],
reg_count,
)
# Binary Output
if config[CONF_REGISTER_TYPE] == "coil":
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_ADDRESS],
byte_offset,
)
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusBinaryOutput.operator("ptr"), "item"),
(cg.bool_, "x"),
(cg.std_vector.template(cg.uint8).operator("ref"), "payload"),
],
return_type=cg.optional.template(bool),
)
# Float Output
else:
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_ADDRESS],
byte_offset,
config[CONF_VALUE_TYPE],
reg_count,
)
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusFloatOutput.operator("ptr"), "item"),
(cg.float_, "x"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
],
return_type=cg.optional.template(float),
)
await output.register_output(var, config)
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
cg.add(var.set_parent(parent))
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusOutput.operator("ptr"), "item"),
(cg.float_, "x"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
],
return_type=cg.optional.template(float),
)
cg.add(var.set_write_template(template_))

View File

@@ -1,18 +1,17 @@
#include <vector>
#include "modbus_output.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace modbus_controller {
static const char *const TAG = "modbus_controller.output";
void ModbusOutput::setup() {}
/** Write a value to the device
*
*/
void ModbusOutput::write_state(float value) {
void ModbusFloatOutput::write_state(float value) {
std::vector<uint16_t> data;
auto original_value = value;
// Is there are lambda configured?
@@ -39,7 +38,6 @@ void ModbusOutput::write_state(float value) {
ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)",
this->start_address, this->register_count, value, original_value);
// Create and send the write command
// Create and send the write command
ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) {
@@ -51,11 +49,62 @@ void ModbusOutput::write_state(float value) {
parent_->queue_command(write_cmd);
}
void ModbusOutput::dump_config() {
void ModbusFloatOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Modbus Float Output:");
LOG_FLOAT_OUTPUT(this);
ESP_LOGCONFIG(TAG, "Modbus device start address=0x%X register count=%d value type=%hhu", this->start_address,
this->register_count, this->sensor_value_type);
ESP_LOGCONFIG(TAG, " Device start address: 0x%X", this->start_address);
ESP_LOGCONFIG(TAG, " Register count: %d", this->register_count);
ESP_LOGCONFIG(TAG, " Value type: %d", static_cast<int>(this->sensor_value_type));
}
// ModbusBinaryOutput
void ModbusBinaryOutput::write_state(bool state) {
// This will be called every time the user requests a state change.
ModbusCommandItem cmd;
std::vector<uint8_t> data;
// Is there are lambda configured?
if (this->write_transform_func_.has_value()) {
// data is passed by reference
// the lambda can fill the empty vector directly
// in that case the return value is ignored
auto val = (*this->write_transform_func_)(this, state, data);
if (val.has_value()) {
ESP_LOGV(TAG, "Value overwritten by lambda");
state = val.value();
} else {
ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
return;
}
}
if (!data.empty()) {
ESP_LOGV(TAG, "Modbus binary output write raw: %s", format_hex_pretty(data).c_str());
cmd = ModbusCommandItem::create_custom_command(
this->parent_, data,
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
});
} else {
ESP_LOGV(TAG, "Write new state: value is %s, type is %d address = %X, offset = %x", ONOFF(state),
(int) this->register_type, this->start_address, this->offset);
// offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) {
std::vector<bool> states{state};
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
} else {
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
}
}
this->parent_->queue_command(cmd);
}
void ModbusBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Modbus Binary Output:");
LOG_BINARY_OUTPUT(this);
ESP_LOGCONFIG(TAG, " Device start address: 0x%X", this->start_address);
ESP_LOGCONFIG(TAG, " Register count: %d", this->register_count);
ESP_LOGCONFIG(TAG, " Value type: %d", static_cast<int>(this->sensor_value_type));
}
} // namespace modbus_controller

View File

@@ -7,11 +7,9 @@
namespace esphome {
namespace modbus_controller {
using value_to_data_t = std::function<float>(float);
class ModbusOutput : public output::FloatOutput, public Component, public SensorItem {
class ModbusFloatOutput : public output::FloatOutput, public Component, public SensorItem {
public:
ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count)
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count)
: output::FloatOutput(), Component() {
this->register_type = ModbusRegisterType::HOLDING;
this->start_address = start_address;
@@ -23,7 +21,6 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor
this->start_address += offset;
this->offset = 0;
}
void setup() override;
void dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; }
@@ -31,7 +28,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<float>(ModbusOutput *, float, std::vector<uint16_t> &)>;
using write_transform_func_t = std::function<optional<float>(ModbusFloatOutput *, float, std::vector<uint16_t> &)>;
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
@@ -44,5 +41,35 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor
bool use_write_multiple_;
};
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem {
public:
ModbusBinaryOutput(uint16_t start_address, uint8_t offset) : output::BinaryOutput(), Component() {
this->register_type = ModbusRegisterType::COIL;
this->start_address = start_address;
this->bitmask = bitmask;
this->sensor_value_type = SensorValueType::BIT;
this->skip_updates = 0;
this->register_count = 1;
this->start_address += offset;
this->offset = 0;
}
void dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; }
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<bool>(ModbusBinaryOutput *, bool, std::vector<uint8_t> &)>;
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:
void write_state(bool state) override;
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_;
bool use_write_multiple_;
};
} // namespace modbus_controller
} // namespace esphome

View File

@@ -14,18 +14,18 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te
void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
std::ostringstream output;
uint8_t max_items = this->response_bytes;
uint8_t index = this->offset;
char buffer[4];
bool add_comma = false;
for (auto b : data) {
while ((max_items != 0) && index < data.size()) {
uint8_t b = data[index];
switch (this->encode_) {
case RawEncoding::HEXBYTES:
sprintf(buffer, "%02x", b);
output << buffer;
break;
case RawEncoding::COMMA:
sprintf(buffer, add_comma ? ",%d" : "%d", b);
sprintf(buffer, index != this->offset ? ",%d" : "%d", b);
output << buffer;
add_comma = true;
break;
// Anything else no encoding
case RawEncoding::NONE:
@@ -33,9 +33,8 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
output << (char) b;
break;
}
if (--max_items == 0) {
break;
}
index++;
}
auto result = output.str();

View File

@@ -80,7 +80,7 @@ MQTTMessageTrigger = mqtt_ns.class_(
"MQTTMessageTrigger", automation.Trigger.template(cg.std_string), cg.Component
)
MQTTJsonMessageTrigger = mqtt_ns.class_(
"MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConstRef)
"MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConst)
)
MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component)
MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition)
@@ -311,7 +311,7 @@ async def to_code(config):
for conf in config.get(CONF_ON_JSON_MESSAGE, []):
trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC], conf[CONF_QOS])
await automation.build_automation(trig, [(cg.JsonObjectConstRef, "x")], conf)
await automation.build_automation(trig, [(cg.JsonObjectConst, "x")], conf)
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
@@ -363,7 +363,7 @@ async def mqtt_publish_json_action_to_code(config, action_id, template_arg, args
template_ = await cg.templatable(config[CONF_TOPIC], args, cg.std_string)
cg.add(var.set_topic(template_))
args_ = args + [(cg.JsonObjectRef, "root")]
args_ = args + [(cg.JsonObject, "root")]
lambda_ = await cg.process_lambda(config[CONF_PAYLOAD], args_, return_type=cg.void)
cg.add(var.set_payload(lambda_))
template_ = await cg.templatable(config[CONF_QOS], args, cg.uint8)

View File

@@ -74,9 +74,9 @@ class CustomMQTTDevice {
* }
*
* // topic parameter can be remove if not needed:
* // e.g.: void on_json_message(JsonObject &payload) {
* // e.g.: void on_json_message(JsonObject payload) {
*
* void on_json_message(const std::string &topic, JsonObject &payload) {
* void on_json_message(const std::string &topic, JsonObject payload) {
* // do something with topic and payload
* if (payload["number"] == 1) {
* digitalWrite(5, HIGH);
@@ -93,11 +93,9 @@ class CustomMQTTDevice {
* @param qos The Quality of Service to subscribe with. Defaults to 0.
*/
template<typename T>
void subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &),
uint8_t qos = 0);
void subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject), uint8_t qos = 0);
template<typename T>
void subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos = 0);
template<typename T> void subscribe_json(const std::string &topic, void (T::*callback)(JsonObject), uint8_t qos = 0);
/** Publish an MQTT message with the given payload and QoS and retain settings.
*
@@ -155,7 +153,7 @@ class CustomMQTTDevice {
*
* ```cpp
* void in_some_method() {
* publish("the/topic", [=](JsonObject &root) {
* publish("the/topic", [=](JsonObject root) {
* root["the_key"] = "Hello World!";
* }, 0, false);
* }
@@ -174,7 +172,7 @@ class CustomMQTTDevice {
*
* ```cpp
* void in_some_method() {
* publish("the/topic", [=](JsonObject &root) {
* publish("the/topic", [=](JsonObject root) {
* root["the_key"] = "Hello World!";
* });
* }
@@ -205,13 +203,13 @@ template<typename T> void CustomMQTTDevice::subscribe(const std::string &topic,
global_mqtt_client->subscribe(topic, f, qos);
}
template<typename T>
void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject &),
void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(const std::string &, JsonObject),
uint8_t qos) {
auto f = std::bind(callback, (T *) this, std::placeholders::_1, std::placeholders::_2);
global_mqtt_client->subscribe_json(topic, f, qos);
}
template<typename T>
void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(JsonObject &), uint8_t qos) {
void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callback)(JsonObject), uint8_t qos) {
auto f = std::bind(callback, (T *) this, std::placeholders::_2);
global_mqtt_client->subscribe_json(topic, f, qos);
}

View File

@@ -29,7 +29,7 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
}
}
void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->binary_sensor_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
if (this->binary_sensor_->is_status_binary_sensor())

View File

@@ -22,7 +22,7 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent {
void dump_config() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
void set_is_status(bool status);

View File

@@ -17,7 +17,7 @@ MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent
void MQTTButtonComponent::setup() {
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
if (payload == "press") {
if (payload == "PRESS") {
this->button_->press();
} else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
@@ -30,7 +30,8 @@ void MQTTButtonComponent::dump_config() {
LOG_MQTT_COMPONENT(true, true);
}
void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
config.state_topic = false;
if (!this->button_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
}

View File

@@ -23,7 +23,7 @@ class MQTTButtonComponent : public mqtt::MQTTComponent {
/// Buttons do not send a state so just return true.
bool send_initial_state() override { return true; }
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
protected:
/// "button" component type.

View File

@@ -1,4 +1,5 @@
#include "mqtt_client.h"
#define USE_MQTT
#ifdef USE_MQTT
@@ -346,7 +347,7 @@ void MQTTClientComponent::subscribe(const std::string &topic, mqtt_callback_t ca
void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
auto f = [callback](const std::string &topic, const std::string &payload) {
json::parse_json(payload, [topic, callback](JsonObject &root) { callback(topic, root); });
json::parse_json(payload, [topic, callback](JsonObject root) { callback(topic, root); });
};
MQTTSubscription subscription{
.topic = topic,
@@ -416,9 +417,8 @@ bool MQTTClientComponent::publish(const MQTTMessage &message) {
}
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
bool retain) {
size_t len;
const char *message = json::build_json(f, &len);
return this->publish(topic, message, len, qos, retain);
std::string message = json::build_json(f);
return this->publish(topic, message, qos, retain);
}
/** Check if the message topic matches the given subscription topic

View File

@@ -20,7 +20,7 @@ namespace mqtt {
* First parameter is the topic, the second one is the payload.
*/
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 &)>;
using mqtt_json_callback_t = std::function<void(const std::string &, JsonObject)>;
/// internal struct for MQTT messages.
struct MQTTMessage {
@@ -306,11 +306,11 @@ class MQTTMessageTrigger : public Trigger<std::string>, public Component {
optional<std::string> payload_;
};
class MQTTJsonMessageTrigger : public Trigger<const JsonObject &> {
class MQTTJsonMessageTrigger : public Trigger<JsonObjectConst> {
public:
explicit MQTTJsonMessageTrigger(const std::string &topic, uint8_t qos) {
global_mqtt_client->subscribe_json(
topic, [this](const std::string &topic, JsonObject &root) { this->trigger(root); }, qos);
topic, [this](const std::string &topic, JsonObject root) { this->trigger(root); }, qos);
}
};
@@ -338,7 +338,7 @@ template<typename... Ts> class MQTTPublishJsonAction : public Action<Ts...> {
TEMPLATABLE_VALUE(uint8_t, qos)
TEMPLATABLE_VALUE(bool, retain)
void set_payload(std::function<void(Ts..., JsonObject &)> payload) { this->payload_ = payload; }
void set_payload(std::function<void(Ts..., JsonObject)> payload) { this->payload_ = payload; }
void play(Ts... x) override {
auto f = std::bind(&MQTTPublishJsonAction<Ts...>::encode_, this, x..., std::placeholders::_1);
@@ -349,8 +349,8 @@ template<typename... Ts> class MQTTPublishJsonAction : public Action<Ts...> {
}
protected:
void encode_(Ts... x, JsonObject &root) { this->payload_(x..., root); }
std::function<void(Ts..., JsonObject &)> payload_;
void encode_(Ts... x, JsonObject root) { this->payload_(x..., root); }
std::function<void(Ts..., JsonObject)> payload_;
MQTTClientComponent *parent_;
};

View File

@@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.climate";
using namespace esphome::climate;
void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
auto traits = this->device_->get_traits();
// current_temperature_topic
if (traits.get_supports_current_temperature()) {
@@ -25,7 +25,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// mode_state_topic
root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
// modes
JsonArray &modes = root.createNestedArray(MQTT_MODES);
JsonArray modes = root.createNestedArray(MQTT_MODES);
// sort array for nice UI in HA
if (traits.supports_mode(CLIMATE_MODE_AUTO))
modes.add("auto");
@@ -83,7 +83,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// fan_mode_state_topic
root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
// fan_modes
JsonArray &fan_modes = root.createNestedArray("fan_modes");
JsonArray fan_modes = root.createNestedArray("fan_modes");
if (traits.supports_fan_mode(CLIMATE_FAN_ON))
fan_modes.add("on");
if (traits.supports_fan_mode(CLIMATE_FAN_OFF))
@@ -112,7 +112,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// swing_mode_state_topic
root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
// swing_modes
JsonArray &swing_modes = root.createNestedArray("swing_modes");
JsonArray swing_modes = root.createNestedArray("swing_modes");
if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
swing_modes.add("off");
if (traits.supports_swing_mode(CLIMATE_SWING_BOTH))

View File

@@ -14,7 +14,7 @@ namespace mqtt {
class MQTTClimateComponent : public mqtt::MQTTComponent {
public:
MQTTClimateComponent(climate::Climate *device);
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;
std::string component_type() const override;
void setup() override;

View File

@@ -63,7 +63,7 @@ bool MQTTComponent::send_discovery_() {
return global_mqtt_client->publish_json(
this->get_discovery_topic_(discovery_info),
[this](JsonObject &root) {
[this](JsonObject root) {
SendDiscoveryConfig config;
config.state_topic = true;
config.command_topic = true;
@@ -127,7 +127,7 @@ bool MQTTComponent::send_discovery_() {
}
}
JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);
JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
device_info[MQTT_DEVICE_NAME] = node_name;
device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();

View File

@@ -70,7 +70,7 @@ class MQTTComponent : public Component {
void call_dump_config() override;
/// Send discovery info the Home Assistant, override this.
virtual void send_discovery(JsonObject &root, SendDiscoveryConfig &config) = 0;
virtual void send_discovery(JsonObject root, SendDiscoveryConfig &config) = 0;
virtual bool send_initial_state() = 0;

View File

@@ -63,7 +63,7 @@ void MQTTCoverComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Tilt Command Topic: '%s'", this->get_tilt_command_topic().c_str());
}
}
void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->cover_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();

View File

@@ -16,7 +16,7 @@ class MQTTCoverComponent : public mqtt::MQTTComponent {
explicit MQTTCoverComponent(cover::Cover *cover);
void setup() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
MQTT_COMPONENT_CUSTOM_TOPIC(position, command)
MQTT_COMPONENT_CUSTOM_TOPIC(position, state)

View File

@@ -120,7 +120,7 @@ void MQTTFanComponent::dump_config() {
bool MQTTFanComponent::send_initial_state() { return this->publish_state(); }
void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (this->state_->get_traits().supports_oscillation()) {
root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();

View File

@@ -22,7 +22,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent {
MQTT_COMPONENT_CUSTOM_TOPIC(speed, command)
MQTT_COMPONENT_CUSTOM_TOPIC(speed, state)
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)

View File

@@ -18,7 +18,7 @@ std::string MQTTJSONLightComponent::component_type() const { return "light"; }
const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; }
void MQTTJSONLightComponent::setup() {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
LightCall call = this->state_->make_call();
LightJSONSchema::parse_json(*this->state_, call, root);
call.perform();
@@ -32,16 +32,16 @@ MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponen
bool MQTTJSONLightComponent::publish_state_() {
return this->publish_json(this->get_state_topic_(),
[this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); });
[this](JsonObject root) { LightJSONSchema::dump_json(*this->state_, root); });
}
LightState *MQTTJSONLightComponent::get_state() const { return this->state_; }
void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
root["schema"] = "json";
auto traits = this->state_->get_traits();
root[MQTT_COLOR_MODE] = true;
JsonArray &color_modes = root.createNestedArray("supported_color_modes");
JsonArray color_modes = root.createNestedArray("supported_color_modes");
if (traits.supports_color_mode(ColorMode::ON_OFF))
color_modes.add("onoff");
if (traits.supports_color_mode(ColorMode::BRIGHTNESS))
@@ -66,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
if (this->state_->supports_effects()) {
root["effect"] = true;
JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST);
JsonArray effect_list = root.createNestedArray(MQTT_EFFECT_LIST);
for (auto *effect : this->state_->get_effects())
effect_list.add(effect->get_name());
effect_list.add("None");

View File

@@ -21,7 +21,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent {
void dump_config() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;

View File

@@ -37,7 +37,7 @@ void MQTTNumberComponent::dump_config() {
std::string MQTTNumberComponent::component_type() const { return "number"; }
const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; }
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = number_->traits;
// https://www.home-assistant.io/integrations/number.mqtt/
root[MQTT_MIN] = traits.get_min_value();

View File

@@ -25,7 +25,7 @@ class MQTTNumberComponent : public mqtt::MQTTComponent {
void setup() override;
void dump_config() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;

View File

@@ -32,10 +32,10 @@ void MQTTSelectComponent::dump_config() {
std::string MQTTSelectComponent::component_type() const { return "select"; }
const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; }
void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = select_->traits;
// https://www.home-assistant.io/integrations/select.mqtt/
JsonArray &options = root.createNestedArray(MQTT_OPTIONS);
JsonArray options = root.createNestedArray(MQTT_OPTIONS);
for (const auto &option : traits.get_options())
options.add(option);

View File

@@ -25,7 +25,7 @@ class MQTTSelectComponent : public mqtt::MQTTComponent {
void setup() override;
void dump_config() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;

View File

@@ -42,7 +42,7 @@ uint32_t MQTTSensorComponent::get_expire_after() const {
void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; }
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->sensor_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();

View File

@@ -27,7 +27,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent {
/// Disable Home Assistant value expiry.
void disable_expire_after();
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)

View File

@@ -44,7 +44,7 @@ void MQTTSwitchComponent::dump_config() {
std::string MQTTSwitchComponent::component_type() const { return "switch"; }
const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; }
void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (this->switch_->assumed_state())
root[MQTT_OPTIMISTIC] = true;
}

View File

@@ -20,7 +20,7 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent {
void setup() override;
void dump_config() override;
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;

View File

@@ -12,7 +12,7 @@ static const char *const TAG = "mqtt.text_sensor";
using namespace esphome::text_sensor;
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {}
void MQTTTextSensor::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
config.command_topic = false;
}
void MQTTTextSensor::setup() {

View File

@@ -15,7 +15,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent {
public:
explicit MQTTTextSensor(text_sensor::TextSensor *sensor);
void send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
void setup() override;

View File

@@ -329,6 +329,7 @@ void Nextion::process_nextion_commands_() {
break;
case 0x02: // invalid Component ID or name was used
ESP_LOGW(TAG, "Nextion reported component ID or name invalid!");
this->remove_from_q_();
break;
case 0x03: // invalid Page ID or name was used
@@ -387,6 +388,7 @@ void Nextion::process_nextion_commands_() {
}
break;
case 0x1A: // variable name invalid
ESP_LOGW(TAG, "Nextion reported variable name invalid!");
this->remove_from_q_();
break;

View File

@@ -171,7 +171,7 @@ void Nextion::set_component_coordinates(const char *component, int x, int y) {
// Drawing
void Nextion::display_picture(int picture_id, int x_start, int y_start) {
this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id);
this->add_no_result_to_queue_with_printf_("display_picture", "pic %d, %d, %d", x_start, y_start, picture_id);
}
void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) {

View File

@@ -8,6 +8,10 @@
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion_upload";
@@ -158,12 +162,8 @@ void Nextion::upload_tft() {
if (!begin_status) {
this->is_updating_ = false;
ESP_LOGD(TAG, "connection failed");
#ifdef USE_ESP32
if (psramFound())
free(this->transfer_buffer_); // NOLINT
else
#endif
delete this->transfer_buffer_;
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_);
return;
} else {
ESP_LOGD(TAG, "Connected");
@@ -252,7 +252,7 @@ void Nextion::upload_tft() {
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
#ifdef USE_ESP32
uint32_t chunk_size = 8192;
if (psramFound()) {
if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) {
chunk_size = this->content_length_;
} else {
if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand
@@ -269,32 +269,18 @@ void Nextion::upload_tft() {
#endif
if (this->transfer_buffer_ == nullptr) {
#ifdef USE_ESP32
if (psramFound()) {
ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram());
this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size);
if (this->transfer_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size);
this->upload_end_();
}
} else {
#endif
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size];
if (this->transfer_buffer_ == nullptr) { // Try a smaller size
ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
chunk_size = 4096;
ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
this->transfer_buffer_ = new uint8_t[chunk_size];
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
this->transfer_buffer_ = allocator.allocate(chunk_size);
if (this->transfer_buffer_ == nullptr) { // Try a smaller size
ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
chunk_size = 4096;
ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
this->transfer_buffer_ = allocator.allocate(chunk_size);
if (!this->transfer_buffer_)
this->upload_end_();
#ifdef USE_ESP32
}
#endif
if (!this->transfer_buffer_)
this->upload_end_();
}
this->transfer_buffer_size_ = chunk_size;

View File

@@ -1,15 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, switch
from esphome.const import CONF_ID, CONF_OUTPUT
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_RESTORE_MODE
from .. import output_ns
OutputSwitch = output_ns.class_("OutputSwitch", switch.Switch, cg.Component)
OutputSwitchRestoreMode = output_ns.enum("OutputSwitchRestoreMode")
RESTORE_MODES = {
"RESTORE_DEFAULT_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_DEFAULT_OFF,
"RESTORE_DEFAULT_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_DEFAULT_ON,
"ALWAYS_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_ALWAYS_OFF,
"ALWAYS_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_ALWAYS_ON,
"RESTORE_INVERTED_DEFAULT_OFF": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF,
"RESTORE_INVERTED_DEFAULT_ON": OutputSwitchRestoreMode.OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON,
}
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(OutputSwitch),
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
}
).extend(cv.COMPONENT_SCHEMA)
@@ -21,3 +34,5 @@ async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))

View File

@@ -8,15 +8,32 @@ static const char *const TAG = "output.switch";
void OutputSwitch::dump_config() { LOG_SWITCH("", "Output Switch", this); }
void OutputSwitch::setup() {
auto restored = this->get_initial_state();
if (!restored.has_value())
return;
if (*restored) {
this->turn_on();
} else {
this->turn_off();
bool initial_state = false;
switch (this->restore_mode_) {
case OUTPUT_SWITCH_RESTORE_DEFAULT_OFF:
initial_state = this->get_initial_state().value_or(false);
break;
case OUTPUT_SWITCH_RESTORE_DEFAULT_ON:
initial_state = this->get_initial_state().value_or(true);
break;
case OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF:
initial_state = !this->get_initial_state().value_or(true);
break;
case OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON:
initial_state = !this->get_initial_state().value_or(false);
break;
case OUTPUT_SWITCH_ALWAYS_OFF:
initial_state = false;
break;
case OUTPUT_SWITCH_ALWAYS_ON:
initial_state = true;
break;
}
if (initial_state)
this->turn_on();
else
this->turn_off();
}
void OutputSwitch::write_state(bool state) {
if (state) {

View File

@@ -7,10 +7,21 @@
namespace esphome {
namespace output {
enum OutputSwitchRestoreMode {
OUTPUT_SWITCH_RESTORE_DEFAULT_OFF,
OUTPUT_SWITCH_RESTORE_DEFAULT_ON,
OUTPUT_SWITCH_ALWAYS_OFF,
OUTPUT_SWITCH_ALWAYS_ON,
OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_OFF,
OUTPUT_SWITCH_RESTORE_INVERTED_DEFAULT_ON,
};
class OutputSwitch : public switch_::Switch, public Component {
public:
void set_output(BinaryOutput *output) { output_ = output; }
void set_restore_mode(OutputSwitchRestoreMode restore_mode) { restore_mode_ = restore_mode; }
void setup() override;
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
@@ -19,6 +30,7 @@ class OutputSwitch : public switch_::Switch, public Component {
void write_state(bool state) override;
output::BinaryOutput *output_;
OutputSwitchRestoreMode restore_mode_;
};
} // namespace output

View File

@@ -7,7 +7,7 @@ namespace esphome {
namespace prometheus {
void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
AsyncResponseStream *stream = req->beginResponseStream("text/plain");
AsyncResponseStream *stream = req->beginResponseStream("text/plain; version=0.0.4; charset=utf-8");
#ifdef USE_SENSOR
this->sensor_type_(stream);

View File

@@ -0,0 +1,29 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.core import CORE
from esphome.const import (
CONF_ID,
)
CODEOWNERS = ["@esphome/core"]
psram_ns = cg.esphome_ns.namespace("psram")
PsramComponent = psram_ns.class_("PsramComponent", cg.Component)
CONFIG_SCHEMA = cv.All(
cv.Schema({cv.GenerateID(): cv.declare_id(PsramComponent)}), cv.only_on_esp32
)
async def to_code(config):
if CORE.using_arduino:
cg.add_build_flag("-DBOARD_HAS_PSRAM")
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,32 @@
#include "psram.h"
#ifdef USE_ESP32
#include "esphome/core/log.h"
#include <esp_heap_caps.h>
#include <esp_idf_version.h>
namespace esphome {
namespace psram {
static const char *const TAG = "psram";
void PsramComponent::dump_config() {
// Technically this can be false if the PSRAM is full, but heap_caps_get_total_size() isn't always available, and it's
// very unlikely for the PSRAM to be full.
bool available = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0;
ESP_LOGCONFIG(TAG, "PSRAM:");
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
if (available) {
ESP_LOGCONFIG(TAG, " Size: %d MB", heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024 / 1024);
}
#endif
}
} // namespace psram
} // namespace esphome
#endif

View File

@@ -0,0 +1,17 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
namespace esphome {
namespace psram {
class PsramComponent : public Component {
void dump_config() override;
};
} // namespace psram
} // namespace esphome
#endif

View File

@@ -8,7 +8,7 @@ static const char *const TAG = "pulse_counter";
const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"};
#ifdef USE_ESP8266
#ifndef HAS_PCNT
void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) {
const uint32_t now = micros();
const bool discard = now - arg->last_pulse < arg->filter_us;
@@ -43,7 +43,7 @@ pulse_counter_t PulseCounterStorage::read_raw_value() {
}
#endif
#ifdef USE_ESP32
#ifdef HAS_PCNT
bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0;
this->pin = pin;
@@ -96,7 +96,7 @@ bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
}
if (this->filter_us != 0) {
uint16_t filter_val = std::min(this->filter_us * 80u, 1023u);
uint16_t filter_val = std::min(static_cast<unsigned int>(this->filter_us * 80u), 1023u);
ESP_LOGCONFIG(TAG, " Filter Value: %uus (val=%u)", this->filter_us, filter_val);
error = pcnt_set_filter_value(this->pcnt_unit, filter_val);
if (error != ESP_OK) {

View File

@@ -4,8 +4,9 @@
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
#include <driver/pcnt.h>
#define HAS_PCNT
#endif
namespace esphome {
@@ -17,10 +18,9 @@ enum PulseCounterCountMode {
PULSE_COUNTER_DECREMENT,
};
#ifdef USE_ESP32
#ifdef HAS_PCNT
using pulse_counter_t = int16_t;
#endif
#ifdef USE_ESP8266
#else
using pulse_counter_t = int32_t;
#endif
@@ -30,16 +30,15 @@ struct PulseCounterStorage {
static void gpio_intr(PulseCounterStorage *arg);
#ifdef USE_ESP8266
#ifndef HAS_PCNT
volatile pulse_counter_t counter{0};
volatile uint32_t last_pulse{0};
#endif
InternalGPIOPin *pin;
#ifdef USE_ESP32
#ifdef HAS_PCNT
pcnt_unit_t pcnt_unit;
#endif
#ifdef USE_ESP8266
#else
ISRInternalGPIOPin isr_pin;
#endif
PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT};

View File

@@ -7,6 +7,7 @@ namespace pzemac {
static const char *const TAG = "pzemac";
static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42;
static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers
void PZEMAC::on_modbus_data(const std::vector<uint8_t> &data) {
@@ -73,5 +74,12 @@ void PZEMAC::dump_config() {
LOG_SENSOR("", "Power Factor", this->power_factor_sensor_);
}
void PZEMAC::reset_energy_() {
std::vector<uint8_t> cmd;
cmd.push_back(this->address_);
cmd.push_back(PZEM_CMD_RESET_ENERGY);
this->send_raw(cmd);
}
} // namespace pzemac
} // namespace esphome

View File

@@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
@@ -7,6 +8,8 @@
namespace esphome {
namespace pzemac {
template<typename... Ts> class ResetEnergyAction;
class PZEMAC : public PollingComponent, public modbus::ModbusDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
@@ -23,12 +26,25 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice {
void dump_config() override;
protected:
template<typename... Ts> friend class ResetEnergyAction;
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
sensor::Sensor *power_sensor_;
sensor::Sensor *energy_sensor_;
sensor::Sensor *frequency_sensor_;
sensor::Sensor *power_factor_sensor_;
void reset_energy_();
};
template<typename... Ts> class ResetEnergyAction : public Action<Ts...> {
public:
ResetEnergyAction(PZEMAC *pzemac) : pzemac_(pzemac) {}
void play(Ts... x) override { this->pzemac_->reset_energy_(); }
protected:
PZEMAC *pzemac_;
};
} // namespace pzemac

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import sensor, modbus
from esphome.const import (
CONF_CURRENT,
@@ -29,6 +31,9 @@ AUTO_LOAD = ["modbus"]
pzemac_ns = cg.esphome_ns.namespace("pzemac")
PZEMAC = pzemac_ns.class_("PZEMAC", cg.PollingComponent, modbus.ModbusDevice)
# Actions
ResetEnergyAction = pzemac_ns.class_("ResetEnergyAction", automation.Action)
CONFIG_SCHEMA = (
cv.Schema(
{
@@ -75,6 +80,20 @@ CONFIG_SCHEMA = (
)
@automation.register_action(
"pzemac.reset_energy",
ResetEnergyAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(PZEMAC),
}
),
)
async def reset_energy_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -27,6 +27,7 @@ from esphome.const import (
CONF_CARRIER_FREQUENCY,
CONF_RC_CODE_1,
CONF_RC_CODE_2,
CONF_LEVEL,
)
from esphome.core import coroutine
from esphome.jsonschema import jschema_extractor
@@ -234,6 +235,45 @@ async def build_dumpers(config):
return dumpers
# Coolix
(
CoolixData,
CoolixBinarySensor,
CoolixTrigger,
CoolixAction,
CoolixDumper,
) = declare_protocol("Coolix")
COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA)
def coolix_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
CoolixData,
("data", config[CONF_DATA]),
)
)
)
@register_trigger("coolix", CoolixTrigger, CoolixData)
def coolix_trigger(var, config):
pass
@register_dumper("coolix", CoolixDumper)
def coolix_dumper(var, config):
pass
@register_action("coolix", CoolixAction, COOLIX_SCHEMA)
async def coolix_action(var, config, args):
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32)
cg.add(var.set_data(template_))
# Dish
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
"Dish"
@@ -1124,6 +1164,58 @@ async def panasonic_action(var, config, args):
cg.add(var.set_command(template_))
# Nexa
NexaData, NexaBinarySensor, NexaTrigger, NexaAction, NexaDumper = declare_protocol(
"Nexa"
)
NEXA_SCHEMA = cv.Schema(
{
cv.Required(CONF_DEVICE): cv.hex_uint32_t,
cv.Required(CONF_GROUP): cv.hex_uint8_t,
cv.Required(CONF_STATE): cv.hex_uint8_t,
cv.Required(CONF_CHANNEL): cv.hex_uint8_t,
cv.Required(CONF_LEVEL): cv.hex_uint8_t,
}
)
@register_binary_sensor("nexa", NexaBinarySensor, NEXA_SCHEMA)
def nexa_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
NexaData,
("device", config[CONF_DEVICE]),
("group", config[CONF_GROUP]),
("state", config[CONF_STATE]),
("channel", config[CONF_CHANNEL]),
("level", config[CONF_LEVEL]),
)
)
)
@register_trigger("nexa", NexaTrigger, NexaData)
def nexa_trigger(var, config):
pass
@register_dumper("nexa", NexaDumper)
def nexa_dumper(var, config):
pass
@register_action("nexa", NexaAction, NEXA_SCHEMA)
def nexa_action(var, config, args):
cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint32))))
cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8))))
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, cg.uint8))))
cg.add(
var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8)))
)
cg.add(var.set_level((yield cg.templatable(config[CONF_LEVEL], args, cg.uint8))))
# Midea
MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol(
"Midea"

View File

@@ -0,0 +1,84 @@
#include "coolix_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *const TAG = "remote.coolix";
static const int32_t TICK_US = 560;
static const int32_t HEADER_MARK_US = 8 * TICK_US;
static const int32_t HEADER_SPACE_US = 8 * TICK_US;
static const int32_t BIT_MARK_US = 1 * TICK_US;
static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US;
static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US;
static const int32_t FOOTER_MARK_US = 1 * TICK_US;
static const int32_t FOOTER_SPACE_US = 10 * TICK_US;
static void encode_data(RemoteTransmitData *dst, const CoolixData &src) {
// Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (unsigned shift = 16;; shift -= 8) {
// Grab a bytes worth of data.
const uint8_t byte = src >> shift;
// Normal
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
// Inverted
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
// Data end
if (shift == 0)
break;
}
}
void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
encode_data(dst, data);
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
encode_data(dst, data);
dst->mark(FOOTER_MARK_US);
}
static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
uint32_t data = 0;
for (unsigned n = 3;; data <<= 8) {
// Read byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_mark(BIT_MARK_US))
return false;
if (src.expect_space(BIT_ONE_SPACE_US))
data |= mask;
else if (!src.expect_space(BIT_ZERO_SPACE_US))
return false;
}
// Check for inverse byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
return false;
}
// Checking the end of reading
if (--n == 0) {
dst = data;
return true;
}
}
}
optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) {
CoolixData first, second;
if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) &&
data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) &&
decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second)
return first;
return {};
}
void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); }
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "remote_base.h"
namespace esphome {
namespace remote_base {
using CoolixData = uint32_t;
class CoolixProtocol : public RemoteProtocol<CoolixData> {
public:
void encode(RemoteTransmitData *dst, const CoolixData &data) override;
optional<CoolixData> decode(RemoteReceiveData data) override;
void dump(const CoolixData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Coolix)
template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> {
TEMPLATABLE_VALUE(CoolixData, data)
void encode(RemoteTransmitData *dst, Ts... x) override {
CoolixData data = this->data_.value(x...);
CoolixProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -6,89 +6,63 @@ namespace remote_base {
static const char *const TAG = "remote.midea";
static const int32_t TICK_US = 560;
static const int32_t HEADER_MARK_US = 8 * TICK_US;
static const int32_t HEADER_SPACE_US = 8 * TICK_US;
static const int32_t BIT_MARK_US = 1 * TICK_US;
static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US;
static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US;
static const int32_t FOOTER_MARK_US = 1 * TICK_US;
static const int32_t FOOTER_SPACE_US = 10 * TICK_US;
uint8_t MideaData::calc_cs_() const {
uint8_t cs = 0;
for (const uint8_t *it = this->data(); it != this->data() + OFFSET_CS; ++it)
cs -= reverse_bits_8(*it);
return reverse_bits_8(cs);
for (uint8_t idx = 0; idx < OFFSET_CS; idx++)
cs -= reverse_bits(this->data_[idx]);
return reverse_bits(cs);
}
bool MideaData::check_compliment(const MideaData &rhs) const {
const uint8_t *it0 = rhs.data();
for (const uint8_t *it1 = this->data(); it1 != this->data() + this->size(); ++it0, ++it1) {
if (*it0 != ~(*it1))
return false;
}
return true;
bool MideaData::is_compliment(const MideaData &rhs) const {
return std::equal(this->data_.begin(), this->data_.end(), rhs.data_.begin(),
[](const uint8_t &a, const uint8_t &b) { return a + b == 255; });
}
void MideaProtocol::data(RemoteTransmitData *dst, const MideaData &src, bool compliment) {
for (const uint8_t *it = src.data(); it != src.data() + src.size(); ++it) {
const uint8_t data = compliment ? ~(*it) : *it;
for (uint8_t mask = 128; mask; mask >>= 1) {
if (data & mask)
one(dst);
else
zero(dst);
}
}
}
void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &data) {
void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 2);
MideaProtocol::header(dst);
MideaProtocol::data(dst, data);
MideaProtocol::footer(dst);
MideaProtocol::header(dst);
MideaProtocol::data(dst, data, true);
MideaProtocol::footer(dst);
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 1);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
for (unsigned idx = 0; idx < 6; idx++)
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
for (unsigned idx = 0; idx < 6; idx++)
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
dst->mark(FOOTER_MARK_US);
}
bool MideaProtocol::expect_one(RemoteReceiveData &src) {
if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US))
return false;
src.advance(2);
return true;
}
bool MideaProtocol::expect_zero(RemoteReceiveData &src) {
if (!src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US))
return false;
src.advance(2);
return true;
}
bool MideaProtocol::expect_header(RemoteReceiveData &src) {
if (!src.peek_item(HEADER_HIGH_US, HEADER_LOW_US))
return false;
src.advance(2);
return true;
}
bool MideaProtocol::expect_footer(RemoteReceiveData &src) {
if (!src.peek_item(BIT_HIGH_US, MIN_GAP_US))
return false;
src.advance(2);
return true;
}
bool MideaProtocol::expect_data(RemoteReceiveData &src, MideaData &out) {
for (uint8_t *dst = out.data(); dst != out.data() + out.size(); ++dst) {
for (uint8_t mask = 128; mask; mask >>= 1) {
if (MideaProtocol::expect_one(src))
*dst |= mask;
else if (!MideaProtocol::expect_zero(src))
static bool decode_data(RemoteReceiveData &src, MideaData &dst) {
for (unsigned idx = 0; idx < 6; idx++) {
uint8_t data = 0;
for (uint8_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_mark(BIT_MARK_US))
return false;
if (src.expect_space(BIT_ONE_SPACE_US))
data |= mask;
else if (!src.expect_space(BIT_ZERO_SPACE_US))
return false;
}
dst[idx] = data;
}
return true;
}
optional<MideaData> MideaProtocol::decode(RemoteReceiveData src) {
MideaData out, inv;
if (MideaProtocol::expect_header(src) && MideaProtocol::expect_data(src, out) && MideaProtocol::expect_footer(src) &&
out.is_valid() && MideaProtocol::expect_data(src, inv) && out.check_compliment(inv))
if (src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(src, out) && out.is_valid() &&
src.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) &&
decode_data(src, inv) && src.expect_mark(FOOTER_MARK_US) && out.is_compliment(inv))
return out;
return {};
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "remote_base.h"
@@ -9,70 +10,61 @@ namespace remote_base {
class MideaData {
public:
// Make zero-filled
MideaData() { memset(this->data_, 0, sizeof(this->data_)); }
// Make default
MideaData() {}
// Make from initializer_list
MideaData(std::initializer_list<uint8_t> data) { std::copy(data.begin(), data.end(), this->data()); }
MideaData(std::initializer_list<uint8_t> data) {
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
}
// Make from vector
MideaData(const std::vector<uint8_t> &data) {
memcpy(this->data_, data.data(), std::min<size_t>(data.size(), sizeof(this->data_)));
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
}
// Default copy constructor
MideaData(const MideaData &) = default;
uint8_t *data() { return this->data_; }
const uint8_t *data() const { return this->data_; }
uint8_t size() const { return sizeof(this->data_); }
uint8_t *data() { return this->data_.data(); }
const uint8_t *data() const { return this->data_.data(); }
uint8_t size() const { return this->data_.size(); }
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
bool check_compliment(const MideaData &rhs) const;
std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); }
bool is_compliment(const MideaData &rhs) const;
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); }
// compare only 40-bits
bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); }
bool operator==(const MideaData &rhs) const {
return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin());
}
enum MideaDataType : uint8_t {
MIDEA_TYPE_COMMAND = 0xA1,
MIDEA_TYPE_CONTROL = 0xA1,
MIDEA_TYPE_SPECIAL = 0xA2,
MIDEA_TYPE_FOLLOW_ME = 0xA4,
};
MideaDataType type() const { return static_cast<MideaDataType>(this->data_[0]); }
template<typename T> T to() const { return T(*this); }
uint8_t &operator[](size_t idx) { return this->data_[idx]; }
const uint8_t &operator[](size_t idx) const { return this->data_[idx]; }
protected:
void set_value_(uint8_t offset, uint8_t val_mask, uint8_t shift, uint8_t val) {
data_[offset] &= ~(val_mask << shift);
data_[offset] |= (val << shift);
uint8_t get_value_(uint8_t idx, uint8_t mask = 255, uint8_t shift = 0) const {
return (this->data_[idx] >> shift) & mask;
}
void set_value_(uint8_t idx, uint8_t value, uint8_t mask = 255, uint8_t shift = 0) {
this->data_[idx] &= ~(mask << shift);
this->data_[idx] |= (value << shift);
}
void set_mask_(uint8_t idx, bool state, uint8_t mask = 255) { this->set_value_(idx, state ? mask : 0, mask); }
static const uint8_t OFFSET_CS = 5;
// 48-bits data
uint8_t data_[6];
std::array<uint8_t, 6> data_;
// Calculate checksum
uint8_t calc_cs_() const;
};
class MideaProtocol : public RemoteProtocol<MideaData> {
public:
void encode(RemoteTransmitData *dst, const MideaData &data) override;
void encode(RemoteTransmitData *dst, const MideaData &src) override;
optional<MideaData> decode(RemoteReceiveData src) override;
void dump(const MideaData &data) override;
protected:
static const int32_t TICK_US = 560;
static const int32_t HEADER_HIGH_US = 8 * TICK_US;
static const int32_t HEADER_LOW_US = 8 * TICK_US;
static const int32_t BIT_HIGH_US = 1 * TICK_US;
static const int32_t BIT_ONE_LOW_US = 3 * TICK_US;
static const int32_t BIT_ZERO_LOW_US = 1 * TICK_US;
static const int32_t MIN_GAP_US = 10 * TICK_US;
static void one(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); }
static void zero(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); }
static void header(RemoteTransmitData *dst) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); }
static void footer(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, MIN_GAP_US); }
static void data(RemoteTransmitData *dst, const MideaData &src, bool compliment = false);
static bool expect_one(RemoteReceiveData &src);
static bool expect_zero(RemoteReceiveData &src);
static bool expect_header(RemoteReceiveData &src);
static bool expect_footer(RemoteReceiveData &src);
static bool expect_data(RemoteReceiveData &src, MideaData &out);
};
class MideaBinarySensor : public RemoteReceiverBinarySensorBase {

View File

@@ -0,0 +1,235 @@
#include "nexa_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *const TAG = "remote.nexa";
static const uint8_t NBITS = 32;
static const uint32_t HEADER_HIGH_US = 319;
static const uint32_t HEADER_LOW_US = 2610;
static const uint32_t BIT_HIGH_US = 319;
static const uint32_t BIT_ONE_LOW_US = 1000;
static const uint32_t BIT_ZERO_LOW_US = 140;
static const uint32_t TX_HEADER_HIGH_US = 250;
static const uint32_t TX_HEADER_LOW_US = TX_HEADER_HIGH_US * 10;
static const uint32_t TX_BIT_HIGH_US = 250;
static const uint32_t TX_BIT_ONE_LOW_US = TX_BIT_HIGH_US * 5;
static const uint32_t TX_BIT_ZERO_LOW_US = TX_BIT_HIGH_US * 1;
void NexaProtocol::one(RemoteTransmitData *dst) const {
// '1' => '10'
dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US);
dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
}
void NexaProtocol::zero(RemoteTransmitData *dst) const {
// '0' => '01'
dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US);
}
void NexaProtocol::sync(RemoteTransmitData *dst) const { dst->item(TX_HEADER_HIGH_US, TX_HEADER_LOW_US); }
void NexaProtocol::encode(RemoteTransmitData *dst, const NexaData &data) {
dst->set_carrier_frequency(0);
// Send SYNC
this->sync(dst);
// Device (26 bits)
for (int16_t i = 26 - 1; i >= 0; i--) {
if (data.device & (1 << i))
this->one(dst);
else
this->zero(dst);
}
// Group (1 bit)
if (data.group != 0)
this->one(dst);
else
this->zero(dst);
// State (1 bit)
if (data.state == 2) {
// Special case for dimmers...send 00 as state
dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
} else if (data.state == 1)
this->one(dst);
else
this->zero(dst);
// Channel (4 bits)
for (int16_t i = 4 - 1; i >= 0; i--) {
if (data.channel & (1 << i))
this->one(dst);
else
this->zero(dst);
}
// Level (4 bits)
if (data.state == 2) {
for (int16_t i = 4 - 1; i >= 0; i--) {
if (data.level & (1 << i))
this->one(dst);
else
this->zero(dst);
}
}
// Send finishing Zero
dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
}
optional<NexaData> NexaProtocol::decode(RemoteReceiveData src) {
NexaData out{
.device = 0,
.group = 0,
.state = 0,
.channel = 0,
.level = 0,
};
// From: http://tech.jolowe.se/home-automation-rf-protocols/
// New data: http://tech.jolowe.se/old-home-automation-rf-protocols/
/*
SHHHH HHHH HHHH HHHH HHHH HHHH HHGO EE BB DDDD 0 P
S = Sync bit.
H = The first 26 bits are transmitter unique codes, and it is this code that the reciever "learns" to recognize.
G = Group code, set to one for the whole group.
O = On/Off bit. Set to 1 for on, 0 for off.
E = Unit to be turned on or off. The code is inverted, i.e. '11' equals 1, '00' equals 4.
B = Button code. The code is inverted, i.e. '11' equals 1, '00' equals 4.
D = Dim level bits.
0 = packet always ends with a zero.
P = Pause, a 10 ms pause in between re-send.
Update: First of all the '1' and '0' bit seems to be reversed (and be the same as Jula I protocol below), i.e.
*/
// Require a SYNC pulse + long gap
if (!src.expect_pulse_with_gap(HEADER_HIGH_US, HEADER_LOW_US))
return {};
// Device
for (uint8_t i = 0; i < 26; i++) {
out.device <<= 1UL;
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
// '1' => '10'
out.device |= 0x01;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
// '0' => '01'
out.device |= 0x00;
} else {
// This should not happen...failed command
return {};
}
}
// GROUP
for (uint8_t i = 0; i < 1; i++) {
out.group <<= 1UL;
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
// '1' => '10'
out.group |= 0x01;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
// '0' => '01'
out.group |= 0x00;
} else {
// This should not happen...failed command
return {};
}
}
// STATE
for (uint8_t i = 0; i < 1; i++) {
out.state <<= 1UL;
// Special treatment as we should handle 01, 10 and 00
// We need to care for the advance made in the expect functions
// hence take them one at a time so that we do not get out of sync
// in decoding
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
// Starts with '1'
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
// '10' => 1
out.state |= 0x01;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
// '11' => NOT OK
// This case is here to make sure we advance through the correct index
// This should not happen...failed command
return {};
}
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
// Starts with '0'
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
// '01' => 0
out.state |= 0x00;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
// '00' => Special case for dimmer! => 2
out.state |= 0x02;
}
}
}
// CHANNEL (EE and BB bits)
for (uint8_t i = 0; i < 4; i++) {
out.channel <<= 1UL;
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
// '1' => '10'
out.channel |= 0x01;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
// '0' => '01'
out.channel |= 0x00;
} else {
// This should not happen...failed command
return {};
}
}
// Optional to transmit LEVEL data (8 bits more)
if (int32_t(src.get_index() + 8) >= src.size()) {
return out;
}
// LEVEL
for (uint8_t i = 0; i < 4; i++) {
out.level <<= 1UL;
if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
// '1' => '10'
out.level |= 0x01;
} else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
(src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
// '0' => '01'
out.level |= 0x00;
} else {
// This should not happen...failed command
break;
}
}
return out;
}
void NexaProtocol::dump(const NexaData &data) {
ESP_LOGD(TAG, "Received NEXA: device=0x%04X group=%d state=%d channel=%d level=%d", data.device, data.group,
data.state, data.channel, data.level);
}
} // namespace remote_base
} // namespace esphome

View File

@@ -0,0 +1,52 @@
#pragma once
#include "remote_base.h"
namespace esphome {
namespace remote_base {
struct NexaData {
uint32_t device;
uint8_t group;
uint8_t state;
uint8_t channel;
uint8_t level;
bool operator==(const NexaData &rhs) const {
return device == rhs.device && group == rhs.group && state == rhs.state && channel == rhs.channel &&
level == rhs.level;
}
};
class NexaProtocol : public RemoteProtocol<NexaData> {
public:
void one(RemoteTransmitData *dst) const;
void zero(RemoteTransmitData *dst) const;
void sync(RemoteTransmitData *dst) const;
void encode(RemoteTransmitData *dst, const NexaData &data) override;
optional<NexaData> decode(RemoteReceiveData src) override;
void dump(const NexaData &data) override;
};
DECLARE_REMOTE_PROTOCOL(Nexa)
template<typename... Ts> class NexaAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint32_t, device)
TEMPLATABLE_VALUE(uint8_t, group)
TEMPLATABLE_VALUE(uint8_t, state)
TEMPLATABLE_VALUE(uint8_t, channel)
TEMPLATABLE_VALUE(uint8_t, level)
void encode(RemoteTransmitData *dst, Ts... x) override {
NexaData data{};
data.device = this->device_.value(x...);
data.group = this->group_.value(x...);
data.state = this->state_.value(x...);
data.channel = this->channel_.value(x...);
data.level = this->level_.value(x...);
NexaProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View File

@@ -116,6 +116,16 @@ class RemoteReceiveData {
return false;
}
bool expect_pulse_with_gap(uint32_t mark, uint32_t space) {
if (this->peek_mark(mark, 0) && this->peek_space_at_least(space, 1)) {
this->advance(2);
return true;
}
return false;
}
uint32_t get_index() { return index_; }
void reset() { this->index_ = 0; }
int32_t pos(uint32_t index) const { return (*this->data_)[index]; }

View File

@@ -1,5 +1,5 @@
import esphome.codegen as cg
CODEOWNERS = ["@paulmonigatti"]
CODEOWNERS = ["@paulmonigatti", "@jsuanet"]
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import button
from esphome.components.ota import OTAComponent
from esphome.const import (
CONF_ID,
CONF_OTA,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ICON_RESTART_ALERT,
)
DEPENDENCIES = ["ota"]
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component)
CONFIG_SCHEMA = (
button.button_schema(
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
)
.extend({cv.GenerateID(): cv.declare_id(SafeModeButton)})
.extend({cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent)})
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await button.register_button(var, config)
ota = await cg.get_variable(config[CONF_OTA])
cg.add(var.set_ota(ota))

View File

@@ -0,0 +1,25 @@
#include "safe_mode_button.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace safe_mode {
static const char *const TAG = "safe_mode.button";
void SafeModeButton::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; }
void SafeModeButton::press_action() {
ESP_LOGI(TAG, "Restarting device in safe mode...");
this->ota_->set_safe_mode_pending(true);
// Let MQTT settle a bit
delay(100); // NOLINT
App.safe_reboot();
}
void SafeModeButton::dump_config() { LOG_BUTTON("", "Safe Mode Button", this); }
} // namespace safe_mode
} // namespace esphome

View File

@@ -0,0 +1,21 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ota/ota_component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace safe_mode {
class SafeModeButton : public button::Button, public Component {
public:
void dump_config() override;
void set_ota(ota::OTAComponent *ota);
protected:
ota::OTAComponent *ota_;
void press_action() override;
};
} // namespace safe_mode
} // namespace esphome

View File

@@ -57,15 +57,19 @@ void SDMMeter::on_modbus_data(const std::vector<uint8_t> &data) {
phase.phase_angle_sensor_->publish_state(phase_angle);
}
float total_power = sdm_meter_get_float(SDM_TOTAL_SYSTEM_POWER * 2);
float frequency = sdm_meter_get_float(SDM_FREQUENCY * 2);
float import_active_energy = sdm_meter_get_float(SDM_IMPORT_ACTIVE_ENERGY * 2);
float export_active_energy = sdm_meter_get_float(SDM_EXPORT_ACTIVE_ENERGY * 2);
float import_reactive_energy = sdm_meter_get_float(SDM_IMPORT_REACTIVE_ENERGY * 2);
float export_reactive_energy = sdm_meter_get_float(SDM_EXPORT_REACTIVE_ENERGY * 2);
ESP_LOGD(TAG, "SDMMeter: F=%.3f Hz, Im.A.E=%.3f Wh, Ex.A.E=%.3f Wh, Im.R.E=%.3f VARh, Ex.R.E=%.3f VARh", frequency,
import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy);
ESP_LOGD(TAG, "SDMMeter: F=%.3f Hz, Im.A.E=%.3f Wh, Ex.A.E=%.3f Wh, Im.R.E=%.3f VARh, Ex.R.E=%.3f VARh, T.P=%.3f W",
frequency, import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy,
total_power);
if (this->total_power_sensor_ != nullptr)
this->total_power_sensor_->publish_state(total_power);
if (this->frequency_sensor_ != nullptr)
this->frequency_sensor_->publish_state(frequency);
if (this->import_active_energy_sensor_ != nullptr)
@@ -95,6 +99,7 @@ void SDMMeter::dump_config() {
LOG_SENSOR(" ", "Power Factor", phase.power_factor_sensor_);
LOG_SENSOR(" ", "Phase Angle", phase.phase_angle_sensor_);
}
LOG_SENSOR(" ", "Total Power", this->total_power_sensor_);
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
LOG_SENSOR(" ", "Import Active Energy", this->import_active_energy_sensor_);
LOG_SENSOR(" ", "Export Active Energy", this->export_active_energy_sensor_);

View File

@@ -37,6 +37,7 @@ class SDMMeter : public PollingComponent, public modbus::ModbusDevice {
this->phases_[phase].setup = true;
this->phases_[phase].phase_angle_sensor_ = phase_angle_sensor;
}
void set_total_power_sensor(sensor::Sensor *total_power_sensor) { this->total_power_sensor_ = total_power_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { this->frequency_sensor_ = frequency_sensor; }
void set_import_active_energy_sensor(sensor::Sensor *import_active_energy_sensor) {
this->import_active_energy_sensor_ = import_active_energy_sensor;
@@ -69,6 +70,7 @@ class SDMMeter : public PollingComponent, public modbus::ModbusDevice {
sensor::Sensor *phase_angle_sensor_{nullptr};
} phases_[3];
sensor::Sensor *frequency_sensor_{nullptr};
sensor::Sensor *total_power_sensor_{nullptr};
sensor::Sensor *import_active_energy_sensor_{nullptr};
sensor::Sensor *export_active_energy_sensor_{nullptr};
sensor::Sensor *import_reactive_energy_sensor_{nullptr};

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_CURRENT,
CONF_EXPORT_ACTIVE_ENERGY,
CONF_EXPORT_REACTIVE_ENERGY,
CONF_TOTAL_POWER,
CONF_FREQUENCY,
CONF_ID,
CONF_IMPORT_ACTIVE_ENERGY,
@@ -98,6 +99,12 @@ CONFIG_SCHEMA = (
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=2,
@@ -132,6 +139,10 @@ async def to_code(config):
await cg.register_component(var, config)
await modbus.register_modbus_device(var, config)
if CONF_TOTAL_POWER in config:
sens = await sensor.new_sensor(config[CONF_TOTAL_POWER])
cg.add(var.set_total_power_sensor(sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_frequency_sensor(sens))

View File

@@ -24,7 +24,10 @@ Sensor::Sensor() : Sensor("") {}
std::string Sensor::get_unit_of_measurement() {
if (this->unit_of_measurement_.has_value())
return *this->unit_of_measurement_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->unit_of_measurement();
#pragma GCC diagnostic pop
}
void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) {
this->unit_of_measurement_ = unit_of_measurement;
@@ -34,7 +37,10 @@ std::string Sensor::unit_of_measurement() { return ""; }
int8_t Sensor::get_accuracy_decimals() {
if (this->accuracy_decimals_.has_value())
return *this->accuracy_decimals_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->accuracy_decimals();
#pragma GCC diagnostic pop
}
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
int8_t Sensor::accuracy_decimals() { return 0; }
@@ -42,7 +48,10 @@ int8_t Sensor::accuracy_decimals() { return 0; }
std::string Sensor::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->device_class();
#pragma GCC diagnostic pop
}
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Sensor::device_class() { return ""; }
@@ -51,7 +60,10 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class_ = stat
StateClass Sensor::get_state_class() {
if (this->state_class_.has_value())
return *this->state_class_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->state_class();
#pragma GCC diagnostic pop
}
StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; }

View File

@@ -150,16 +150,28 @@ class Sensor : public EntityBase {
void internal_send_state_to_frontend(float state);
protected:
/// Override this to set the default unit of measurement.
/** Override this to set the default unit of measurement.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual std::string unit_of_measurement(); // NOLINT
/// Override this to set the default accuracy in decimals.
/** Override this to set the default accuracy in decimals.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual int8_t accuracy_decimals(); // NOLINT
/// Override this to set the default device class.
/** Override this to set the default device class.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual std::string device_class(); // NOLINT
/// Override this to set the default state class.
/** Override this to set the default state class.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual StateClass state_class(); // NOLINT
uint32_t hash_base() override;

View File

@@ -211,7 +211,7 @@ uint16_t SGP40Component::measure_raw_() {
ESP_LOGD(TAG, "write error");
return UINT16_MAX;
}
delay(250); // NOLINT
delay(30);
uint16_t raw_data[1];
if (!this->read_data_(raw_data, 1)) {

View File

@@ -1 +1 @@
CODEOWNERS = ["@esphome/core"]
CODEOWNERS = ["@esphome/core", "@jsuanet"]

View File

@@ -1,7 +1,7 @@
#include "shutdown_button.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#ifdef USE_ESP32
#include <esp_sleep.h>
@@ -15,10 +15,11 @@ namespace shutdown {
static const char *const TAG = "shutdown.button";
void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); }
void ShutdownButton::press_action() {
ESP_LOGI(TAG, "Shutting down...");
// Let MQTT settle a bit
delay(100); // NOLINT
App.run_safe_shutdown_hooks();
#ifdef USE_ESP8266
ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance)
@@ -27,7 +28,6 @@ void ShutdownButton::press_action() {
esp_deep_sleep_start();
#endif
}
void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); }
} // namespace shutdown
} // namespace esphome

View File

@@ -1,7 +1,7 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace shutdown {

View File

@@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"sim800l", baud_rate=9600, require_tx=True, require_rx=True
"sim800l", require_tx=True, require_rx=True
)

View File

@@ -2,15 +2,37 @@ from esphome import pins, core
from esphome.components import output
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID, CONF_PIN, CONF_PERIOD
from esphome import automation
from esphome.const import (
CONF_ID,
CONF_PIN,
CONF_PERIOD,
CONF_TURN_ON_ACTION,
CONF_TURN_OFF_ACTION,
)
slow_pwm_ns = cg.esphome_ns.namespace("slow_pwm")
SlowPWMOutput = slow_pwm_ns.class_("SlowPWMOutput", output.FloatOutput, cg.Component)
CONF_STATE_CHANGE_ACTION = "state_change_action"
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(SlowPWMOutput),
cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_PIN): pins.gpio_output_pin_schema,
cv.Inclusive(
CONF_TURN_ON_ACTION,
"on_off",
f"{CONF_TURN_ON_ACTION} and {CONF_TURN_OFF_ACTION} must both be defined",
): automation.validate_automation(single=True),
cv.Inclusive(
CONF_TURN_OFF_ACTION,
"on_off",
f"{CONF_TURN_ON_ACTION} and {CONF_TURN_OFF_ACTION} must both be defined",
): automation.validate_automation(single=True),
cv.Optional(CONF_STATE_CHANGE_ACTION): automation.validate_automation(
single=True
),
cv.Required(CONF_PERIOD): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(min=core.TimePeriod(milliseconds=100)),
@@ -23,7 +45,21 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await output.register_output(var, config)
if CONF_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if CONF_STATE_CHANGE_ACTION in config:
await automation.build_automation(
var.get_state_change_trigger(),
[(bool, "state")],
config[CONF_STATE_CHANGE_ACTION],
)
if CONF_TURN_ON_ACTION in config:
await automation.build_automation(
var.get_turn_on_trigger(), [], config[CONF_TURN_ON_ACTION]
)
await automation.build_automation(
var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION]
)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
cg.add(var.set_period(config[CONF_PERIOD]))

View File

@@ -7,34 +7,59 @@ namespace slow_pwm {
static const char *const TAG = "output.slow_pwm";
void SlowPWMOutput::setup() {
this->pin_->setup();
if (this->pin_)
this->pin_->setup();
this->turn_off();
}
/// turn on/off the configured output
void SlowPWMOutput::set_output_state_(bool new_state) {
if (this->pin_) {
this->pin_->digital_write(new_state);
}
if (new_state != current_state_) {
if (this->state_change_trigger_) {
this->state_change_trigger_->trigger(new_state);
}
if (new_state) {
if (this->turn_on_trigger_)
this->turn_on_trigger_->trigger();
} else {
if (this->turn_off_trigger_)
this->turn_off_trigger_->trigger();
}
current_state_ = new_state;
}
}
void SlowPWMOutput::loop() {
uint32_t now = millis();
float scaled_state = this->state_ * this->period_;
if (now - this->period_start_time_ > this->period_) {
if (now - this->period_start_time_ >= this->period_) {
ESP_LOGVV(TAG, "End of period. State: %f, Scaled state: %f", this->state_, scaled_state);
this->period_start_time_ += this->period_;
}
if (scaled_state > now - this->period_start_time_) {
this->pin_->digital_write(true);
this->set_output_state_(true);
} else {
this->pin_->digital_write(false);
this->set_output_state_(false);
}
}
void SlowPWMOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Slow PWM Output:");
LOG_PIN(" Pin: ", this->pin_);
if (this->state_change_trigger_)
ESP_LOGCONFIG(TAG, " State change automation configured");
if (this->turn_on_trigger_)
ESP_LOGCONFIG(TAG, " Turn on automation configured");
if (this->turn_off_trigger_)
ESP_LOGCONFIG(TAG, " Turn off automation configured");
ESP_LOGCONFIG(TAG, " Period: %d ms", this->period_);
LOG_FLOAT_OUTPUT(this);
}
void SlowPWMOutput::write_state(float state) { this->state_ = state; }
} // namespace slow_pwm
} // namespace esphome

View File

@@ -1,5 +1,5 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/output/float_output.h"
@@ -11,19 +11,42 @@ class SlowPWMOutput : public output::FloatOutput, public Component {
public:
void set_pin(GPIOPin *pin) { pin_ = pin; };
void set_period(unsigned int period) { period_ = period; };
/// Initialize pin
void setup() override;
void dump_config() override;
/// HARDWARE setup_priority
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
void write_state(float state) override;
void loop() override;
Trigger<> *get_turn_on_trigger() {
// Lazy create
if (!this->turn_on_trigger_)
this->turn_on_trigger_ = make_unique<Trigger<>>();
return this->turn_on_trigger_.get();
}
Trigger<> *get_turn_off_trigger() {
if (!this->turn_off_trigger_)
this->turn_off_trigger_ = make_unique<Trigger<>>();
return this->turn_off_trigger_.get();
}
GPIOPin *pin_;
Trigger<bool> *get_state_change_trigger() {
if (!this->state_change_trigger_)
this->state_change_trigger_ = make_unique<Trigger<bool>>();
return this->state_change_trigger_.get();
}
protected:
void loop() override;
void write_state(float state) override { state_ = state; }
/// turn on/off the configured output
void set_output_state_(bool state);
GPIOPin *pin_{nullptr};
std::unique_ptr<Trigger<>> turn_on_trigger_{nullptr};
std::unique_ptr<Trigger<>> turn_off_trigger_{nullptr};
std::unique_ptr<Trigger<bool>> state_change_trigger_{nullptr};
float state_{0};
bool current_state_{false};
unsigned int period_start_time_{0};
unsigned int period_{5000};
};

View File

@@ -31,6 +31,7 @@ MODELS = {
"SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64,
"SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16,
"SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48,
"SH1107_128X64": SSD1306Model.SH1107_MODEL_128_64,
"SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32,
"SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64,
}
@@ -61,8 +62,8 @@ SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_EXTERNAL_VCC): cv.boolean,
cv.Optional(CONF_FLIP_X, default=True): cv.boolean,
cv.Optional(CONF_FLIP_Y, default=True): cv.boolean,
cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=15),
cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=15),
cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=-32, max=32),
cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=-32, max=32),
cv.Optional(CONF_INVERT, default=False): cv.boolean,
}
).extend(cv.polling_component_schema("1s"))

View File

@@ -96,6 +96,7 @@ void SSD1306::setup() {
case SSD1306_MODEL_64_48:
case SSD1306_MODEL_64_32:
case SH1106_MODEL_64_48:
case SH1107_MODEL_128_64:
case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64:
this->command(0x12);
@@ -111,7 +112,14 @@ void SSD1306::setup() {
// Set V_COM (0xDB)
this->command(SSD1306_COMMAND_SET_VCOM_DETECT);
this->command(0x00);
switch (this->model_) {
case SH1107_MODEL_128_64:
this->command(0x35);
break;
default:
this->command(0x00);
break;
}
// Display output follow RAM (0xA4)
this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME);
@@ -198,6 +206,8 @@ void SSD1306::turn_off() {
}
int SSD1306::get_height_internal() {
switch (this->model_) {
case SH1107_MODEL_128_64:
return 128;
case SSD1306_MODEL_128_32:
case SSD1306_MODEL_64_32:
case SH1106_MODEL_128_32:
@@ -232,6 +242,7 @@ int SSD1306::get_width_internal() {
case SSD1306_MODEL_64_48:
case SSD1306_MODEL_64_32:
case SH1106_MODEL_64_48:
case SH1107_MODEL_128_64:
return 64;
default:
return 0;
@@ -289,6 +300,8 @@ const char *SSD1306::model_str_() {
return "SH1106 96x16";
case SH1106_MODEL_64_48:
return "SH1106 64x48";
case SH1107_MODEL_128_64:
return "SH1107 128x64";
case SSD1305_MODEL_128_32:
return "SSD1305 128x32";
case SSD1305_MODEL_128_64:

View File

@@ -17,6 +17,7 @@ enum SSD1306Model {
SH1106_MODEL_128_64,
SH1106_MODEL_96_16,
SH1106_MODEL_64_48,
SH1107_MODEL_128_64,
SSD1305_MODEL_128_32,
SSD1305_MODEL_128_64,
};

View File

@@ -265,8 +265,8 @@ void ST7735::setup() {
height_ == 0 ? height_ = ST7735_TFTHEIGHT_160 : height_;
width_ == 0 ? width_ = ST7735_TFTWIDTH_80 : width_;
display_init_(RCMD2GREEN160X80);
colstart_ = 24;
rowstart_ = 0; // For default rotation 0
colstart_ == 0 ? colstart_ = 24 : colstart_;
rowstart_ == 0 ? rowstart_ = 0 : rowstart_;
} else {
// colstart, rowstart left at default '0' values
display_init_(RCMD2RED);

View File

@@ -14,7 +14,7 @@ using st7920_writer_t = std::function<void(ST7920 &)>;
class ST7920 : public PollingComponent,
public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_1MHZ> {
spi::DATA_RATE_200KHZ> {
public:
void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; }
void set_height(uint16_t height) { this->height_ = height; }

View File

@@ -34,7 +34,7 @@ void Switch::publish_state(bool state) {
this->state = state != this->inverted_;
this->rtc_.save(&this->state);
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(state));
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(this->state));
this->state_callback_.call(this->state);
}
bool Switch::assumed_state() { return false; }

View File

@@ -8,6 +8,8 @@ from esphome.const import (
CONF_ID,
CONF_LAMBDA,
CONF_INTENSITY,
CONF_INVERTED,
CONF_LENGTH,
)
CODEOWNERS = ["@glmnet"]
@@ -22,6 +24,8 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_INTENSITY, default=7): cv.All(
cv.uint8_t, cv.Range(min=0, max=7)
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
cv.Optional(CONF_LENGTH, default=6): cv.All(cv.uint8_t, cv.Range(min=1, max=6)),
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema,
}
@@ -39,6 +43,8 @@ async def to_code(config):
cg.add(var.set_dio_pin(dio))
cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_length(config[CONF_LENGTH]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(

View File

@@ -130,7 +130,9 @@ void TM1637Display::setup() {
}
void TM1637Display::dump_config() {
ESP_LOGCONFIG(TAG, "TM1637:");
ESP_LOGCONFIG(TAG, " INTENSITY: %d", this->intensity_);
ESP_LOGCONFIG(TAG, " Intensity: %d", this->intensity_);
ESP_LOGCONFIG(TAG, " Inverted: %d", this->inverted_);
ESP_LOGCONFIG(TAG, " Length: %d", this->length_);
LOG_PIN(" CLK Pin: ", this->clk_pin_);
LOG_PIN(" DIO Pin: ", this->dio_pin_);
LOG_UPDATE_INTERVAL(this);
@@ -173,8 +175,14 @@ void TM1637Display::display() {
this->send_byte_(TM1637_I2C_COMM2);
// Write the data bytes
for (auto b : this->buffer_) {
this->send_byte_(b);
if (this->inverted_) {
for (int8_t i = this->length_ - 1; i >= 0; i--) {
this->send_byte_(this->buffer_[i]);
}
} else {
for (auto b : this->buffer_) {
this->send_byte_(b);
}
}
this->stop_();
@@ -241,14 +249,27 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
}
// Remap segments, for compatibility with MAX7219 segment definition which is
// XABCDEFG, but TM1637 is // XGFEDCBA
data = ((data & 0x80) ? 0x80 : 0) | // no move X
((data & 0x40) ? 0x1 : 0) | // A
((data & 0x20) ? 0x2 : 0) | // B
((data & 0x10) ? 0x4 : 0) | // C
((data & 0x8) ? 0x8 : 0) | // D
((data & 0x4) ? 0x10 : 0) | // E
((data & 0x2) ? 0x20 : 0) | // F
((data & 0x1) ? 0x40 : 0); // G
if (this->inverted_) {
// XABCDEFG > XGCBAFED
data = ((data & 0x80) ? 0x80 : 0) | // no move X
((data & 0x40) ? 0x8 : 0) | // A
((data & 0x20) ? 0x10 : 0) | // B
((data & 0x10) ? 0x20 : 0) | // C
((data & 0x8) ? 0x1 : 0) | // D
((data & 0x4) ? 0x2 : 0) | // E
((data & 0x2) ? 0x4 : 0) | // F
((data & 0x1) ? 0x40 : 0); // G
} else {
// XABCDEFG > XGFEDCBA
data = ((data & 0x80) ? 0x80 : 0) | // no move X
((data & 0x40) ? 0x1 : 0) | // A
((data & 0x20) ? 0x2 : 0) | // B
((data & 0x10) ? 0x4 : 0) | // C
((data & 0x8) ? 0x8 : 0) | // D
((data & 0x4) ? 0x10 : 0) | // E
((data & 0x2) ? 0x20 : 0) | // F
((data & 0x1) ? 0x40 : 0); // G
}
if (*str == '.') {
if (pos != start_pos)
pos--;

View File

@@ -41,6 +41,8 @@ class TM1637Display : public PollingComponent {
uint8_t print(const char *str);
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }
void set_length(uint8_t length) { this->length_ = length; }
void display();
@@ -62,6 +64,8 @@ class TM1637Display : public PollingComponent {
GPIOPin *dio_pin_;
GPIOPin *clk_pin_;
uint8_t intensity_;
uint8_t length_;
bool inverted_;
optional<tm1637_writer_t> writer_{};
uint8_t buffer_[6] = {0};
};

View File

@@ -9,6 +9,8 @@ from esphome.const import (
DEVICE_CLASS_ENERGY,
CONF_METHOD,
STATE_CLASS_TOTAL_INCREASING,
CONF_UNIT_OF_MEASUREMENT,
CONF_ACCURACY_DECIMALS,
)
from esphome.core.entity_helpers import inherit_property_from
@@ -27,6 +29,15 @@ TotalDailyEnergy = total_daily_energy_ns.class_(
"TotalDailyEnergy", sensor.Sensor, cg.Component
)
def inherit_unit_of_measurement(uom, config):
return uom + "h"
def inherit_accuracy_decimals(decimals, config):
return decimals + 2
CONFIG_SCHEMA = (
sensor.sensor_schema(
device_class=DEVICE_CLASS_ENERGY,
@@ -54,11 +65,19 @@ FINAL_VALIDATE_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(TotalDailyEnergy),
cv.Optional(CONF_ICON): cv.icon,
cv.Optional(CONF_UNIT_OF_MEASUREMENT): sensor.validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): sensor.validate_accuracy_decimals,
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
},
extra=cv.ALLOW_EXTRA,
),
inherit_property_from(CONF_ICON, CONF_POWER_ID),
inherit_property_from(
CONF_UNIT_OF_MEASUREMENT, CONF_POWER_ID, transform=inherit_unit_of_measurement
),
inherit_property_from(
CONF_ACCURACY_DECIMALS, CONF_POWER_ID, transform=inherit_accuracy_decimals
),
)

View File

@@ -25,8 +25,6 @@ class TotalDailyEnergy : public sensor::Sensor, public Component {
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
std::string unit_of_measurement() override { return this->parent_->get_unit_of_measurement() + "h"; }
int8_t accuracy_decimals() override { return this->parent_->get_accuracy_decimals() + 2; }
void loop() override;
void publish_state_and_save(float state);

View File

@@ -35,7 +35,7 @@ void TTP229LSFComponent::loop() {
}
touched = i2c::i2ctohs(touched);
this->status_clear_warning();
touched = reverse_bits_16(touched);
touched = reverse_bits(touched);
for (auto *channel : this->channels_) {
channel->process(touched);
}

View File

@@ -0,0 +1,54 @@
from esphome.components import number
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_ID,
CONF_NUMBER_DATAPOINT,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_STEP,
)
from .. import tuya_ns, CONF_TUYA_ID, Tuya
DEPENDENCIES = ["tuya"]
CODEOWNERS = ["@frankiboy1"]
TuyaNumber = tuya_ns.class_("TuyaNumber", number.Number, cg.Component)
def validate_min_max(config):
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
raise cv.Invalid("max_value must be greater than min_value")
return config
CONFIG_SCHEMA = cv.All(
number.NUMBER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TuyaNumber),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Required(CONF_NUMBER_DATAPOINT): cv.uint8_t,
cv.Required(CONF_MAX_VALUE): cv.float_,
cv.Required(CONF_MIN_VALUE): cv.float_,
cv.Required(CONF_STEP): cv.positive_float,
}
).extend(cv.COMPONENT_SCHEMA),
validate_min_max,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await number.register_number(
var,
config,
min_value=config[CONF_MIN_VALUE],
max_value=config[CONF_MAX_VALUE],
step=config[CONF_STEP],
)
paren = await cg.get_variable(config[CONF_TUYA_ID])
cg.add(var.set_tuya_parent(paren))
cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT]))

View File

@@ -0,0 +1,38 @@
#include "esphome/core/log.h"
#include "tuya_number.h"
namespace esphome {
namespace tuya {
static const char *const TAG = "tuya.number";
void TuyaNumber::setup() {
this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) {
if (datapoint.type == TuyaDatapointType::INTEGER) {
ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int);
this->publish_state(datapoint.value_int);
} else if (datapoint.type == TuyaDatapointType::ENUM) {
ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum);
this->publish_state(datapoint.value_enum);
}
this->type_ = datapoint.type;
});
}
void TuyaNumber::control(float value) {
ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value);
if (this->type_ == TuyaDatapointType::INTEGER) {
this->parent_->set_integer_datapoint_value(this->number_id_, value);
} else if (this->type_ == TuyaDatapointType::ENUM) {
this->parent_->set_enum_datapoint_value(this->number_id_, value);
}
this->publish_state(value);
}
void TuyaNumber::dump_config() {
LOG_NUMBER("", "Tuya Number", this);
ESP_LOGCONFIG(TAG, " Number has datapoint ID %u", this->number_id_);
}
} // namespace tuya
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/tuya/tuya.h"
#include "esphome/components/number/number.h"
namespace esphome {
namespace tuya {
class TuyaNumber : public number::Number, public Component {
public:
void setup() override;
void dump_config() override;
void set_number_id(uint8_t number_id) { this->number_id_ = number_id; }
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
protected:
void control(float value) override;
Tuya *parent_;
uint8_t number_id_{0};
TuyaDatapointType type_{};
};
} // namespace tuya
} // namespace esphome

View File

@@ -22,31 +22,38 @@ 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)
CONFIG_SCHEMA = 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_CSS_INCLUDE): cv.file_,
cv.Optional(
CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"
): cv.string,
cv.Optional(CONF_JS_INCLUDE): cv.file_,
cv.Optional(CONF_AUTH): cv.Schema(
{
cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)),
cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)),
}
),
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
cv.Optional(CONF_OTA, default=True): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
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_CSS_INCLUDE): cv.file_,
cv.Optional(
CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"
): cv.string,
cv.Optional(CONF_JS_INCLUDE): cv.file_,
cv.Optional(CONF_AUTH): cv.Schema(
{
cv.Required(CONF_USERNAME): cv.All(
cv.string_strict, cv.Length(min=1)
),
cv.Required(CONF_PASSWORD): cv.All(
cv.string_strict, cv.Length(min=1)
),
}
),
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
cv.Optional(CONF_OTA, default=True): cv.boolean,
},
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
@coroutine_with_priority(40.0)

View File

@@ -236,7 +236,18 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
#ifdef USE_NUMBER
for (auto *obj : App.get_numbers())
if (this->include_internal_ || !obj->is_internal())
write_row(stream, obj, "number", "");
write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) {
number::Number *number = (number::Number *) obj;
stream.print(R"(<input type="number" min=")");
stream.print(number->traits.get_min_value());
stream.print(R"(" max=")");
stream.print(number->traits.get_max_value());
stream.print(R"(" step=")");
stream.print(number->traits.get_step());
stream.print(R"(" value=")");
stream.print(number->state);
stream.print(R"("/>)");
});
#endif
#ifdef USE_SELECT
@@ -316,7 +327,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM
request->send(404);
}
std::string WebServer::sensor_json(sensor::Sensor *obj, float value) {
return json::build_json([obj, value](JsonObject &root) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "sensor-" + obj->get_object_id();
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
if (!obj->get_unit_of_measurement().empty())
@@ -342,7 +353,7 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const
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) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "text_sensor-" + obj->get_object_id();
root["state"] = value;
root["value"] = value;
@@ -355,7 +366,7 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
this->events_.send(this->switch_json(obj, state).c_str(), "state");
}
std::string WebServer::switch_json(switch_::Switch *obj, bool value) {
return json::build_json([obj, value](JsonObject &root) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "switch-" + obj->get_object_id();
root["state"] = value ? "ON" : "OFF";
root["value"] = value;
@@ -410,7 +421,7 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state");
}
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) {
return json::build_json([obj, value](JsonObject &root) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "binary_sensor-" + obj->get_object_id();
root["state"] = value ? "ON" : "OFF";
root["value"] = value;
@@ -431,7 +442,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
#ifdef USE_FAN
void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); }
std::string WebServer::fan_json(fan::FanState *obj) {
return json::build_json([obj](JsonObject &root) {
return json::build_json([obj](JsonObject root) {
root["id"] = "fan-" + obj->get_object_id();
root["state"] = obj->state ? "ON" : "OFF";
root["value"] = obj->state;
@@ -580,7 +591,7 @@ 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) {
return json::build_json([obj](JsonObject root) {
root["id"] = "light-" + obj->get_object_id();
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
light::LightJSONSchema::dump_json(*obj, root);
@@ -632,7 +643,7 @@ 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) {
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;
@@ -652,18 +663,38 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
for (auto *obj : App.get_numbers()) {
if (obj->get_object_id() != match.id)
continue;
std::string data = this->number_json(obj, obj->state);
request->send(200, "text/json", data.c_str());
if (request->method() == HTTP_GET) {
std::string data = this->number_json(obj, obj->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());
if (value_f.has_value())
call.set_value(*value_f);
}
this->defer([call]() mutable { call.perform(); });
request->send(200);
return;
}
request->send(404);
}
std::string WebServer::number_json(number::Number *obj, float value) {
return json::build_json([obj, value](JsonObject &root) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "number-" + obj->get_object_id();
char buffer[64];
snprintf(buffer, sizeof(buffer), "%f", value);
root["state"] = buffer;
std::string state = str_sprintf("%f", value);
root["state"] = state;
root["value"] = value;
});
}
@@ -703,7 +734,7 @@ 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) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "select-" + obj->get_object_id();
root["state"] = value;
root["value"] = value;
@@ -769,7 +800,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
#endif
#ifdef USE_NUMBER
if (request->method() == HTTP_GET && match.domain == "number")
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number")
return true;
#endif

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2022.1.0-dev"
__version__ = "2022.1.0b3"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -402,6 +402,7 @@ CONF_NUM_CHIPS = "num_chips"
CONF_NUM_LEDS = "num_leds"
CONF_NUM_SCANS = "num_scans"
CONF_NUMBER = "number"
CONF_NUMBER_DATAPOINT = "number_datapoint"
CONF_OFF_MODE = "off_mode"
CONF_OFFSET = "offset"
CONF_ON = "on"
@@ -598,6 +599,7 @@ CONF_SHOW_VALUES = "show_values"
CONF_SHUNT_RESISTANCE = "shunt_resistance"
CONF_SHUNT_VOLTAGE = "shunt_voltage"
CONF_SHUTDOWN_MESSAGE = "shutdown_message"
CONF_SIGNAL_STRENGTH = "signal_strength"
CONF_SINGLE_LIGHT_ID = "single_light_id"
CONF_SIZE = "size"
CONF_SLEEP_DURATION = "sleep_duration"
@@ -683,6 +685,7 @@ CONF_TOLERANCE = "tolerance"
CONF_TOPIC = "topic"
CONF_TOPIC_PREFIX = "topic_prefix"
CONF_TOTAL = "total"
CONF_TOTAL_POWER = "total_power"
CONF_TRACES = "traces"
CONF_TRANSITION_LENGTH = "transition_length"
CONF_TRIGGER_ID = "trigger_id"

View File

@@ -21,10 +21,10 @@ template<typename T, typename... X> class TemplatableValue {
public:
TemplatableValue() : type_(EMPTY) {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
TemplatableValue(F value) : type_(VALUE), value_(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
bool has_value() { return this->type_ != EMPTY; }

61
esphome/core/datatypes.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <cstdint>
#include "esphome/core/helpers.h"
namespace esphome {
namespace internal {
/// Wrapper class for memory using big endian data layout, transparently converting it to native order.
template<typename T> class BigEndianLayout {
public:
constexpr14 operator T() { return convert_big_endian(val_); }
private:
T val_;
} __attribute__((packed));
/// Wrapper class for memory using big endian data layout, transparently converting it to native order.
template<typename T> class LittleEndianLayout {
public:
constexpr14 operator T() { return convert_little_endian(val_); }
private:
T val_;
} __attribute__((packed));
} // namespace internal
/// 24-bit unsigned integer type, transparently converting to 32-bit.
struct uint24_t { // NOLINT(readability-identifier-naming)
operator uint32_t() { return val; }
uint32_t val : 24;
} __attribute__((packed));
/// 24-bit signed integer type, transparently converting to 32-bit.
struct int24_t { // NOLINT(readability-identifier-naming)
operator int32_t() { return val; }
int32_t val : 24;
} __attribute__((packed));
// Integer types in big or little endian data layout.
using uint64_be_t = internal::BigEndianLayout<uint64_t>;
using uint32_be_t = internal::BigEndianLayout<uint32_t>;
using uint24_be_t = internal::BigEndianLayout<uint24_t>;
using uint16_be_t = internal::BigEndianLayout<uint16_t>;
using int64_be_t = internal::BigEndianLayout<int64_t>;
using int32_be_t = internal::BigEndianLayout<int32_t>;
using int24_be_t = internal::BigEndianLayout<int24_t>;
using int16_be_t = internal::BigEndianLayout<int16_t>;
using uint64_le_t = internal::LittleEndianLayout<uint64_t>;
using uint32_le_t = internal::LittleEndianLayout<uint32_t>;
using uint24_le_t = internal::LittleEndianLayout<uint24_t>;
using uint16_le_t = internal::LittleEndianLayout<uint16_t>;
using int64_le_t = internal::LittleEndianLayout<int64_t>;
using int32_le_t = internal::LittleEndianLayout<int32_t>;
using int24_le_t = internal::LittleEndianLayout<int24_t>;
using int16_le_t = internal::LittleEndianLayout<int16_t>;
} // namespace esphome

View File

@@ -3,29 +3,54 @@ import esphome.final_validate as fv
from esphome.const import CONF_ID
def inherit_property_from(property_to_inherit, parent_id_property):
def inherit_property_from(property_to_inherit, parent_id_property, transform=None):
"""Validator that inherits a configuration property from another entity, for use with FINAL_VALIDATE_SCHEMA.
If a property is already set, it will not be inherited.
Keyword arguments:
property_to_inherit -- the name of the property to inherit, e.g. CONF_ICON
parent_id_property -- the name of the property that holds the ID of the parent, e.g. CONF_POWER_ID
property_to_inherit -- the name or path of the property to inherit, e.g. CONF_ICON or [CONF_SENSOR, 0, CONF_ICON]
(the parent must exist, otherwise nothing is done).
parent_id_property -- the name or path of the property that holds the ID of the parent, e.g. CONF_POWER_ID or
[CONF_SENSOR, 1, CONF_POWER_ID].
"""
def _walk_config(config, path):
walk = [path] if not isinstance(path, list) else path
for item_or_index in walk:
config = config[item_or_index]
return config
def inherit_property(config):
if property_to_inherit not in config:
# Split the property into its path and name
if not isinstance(property_to_inherit, list):
property_path, property = [], property_to_inherit
else:
property_path, property = property_to_inherit[:-1], property_to_inherit[-1]
# Check if the property to inherit is accessible
try:
config_part = _walk_config(config, property_path)
except KeyError:
return config
# Only inherit the property if it does not exist yet
if property not in config_part:
fconf = fv.full_config.get()
# Get config for the parent entity
path = fconf.get_path_for_id(config[parent_id_property])[:-1]
parent_config = fconf.get_config_for_path(path)
parent_id = _walk_config(config, parent_id_property)
parent_path = fconf.get_path_for_id(parent_id)[:-1]
parent_config = fconf.get_config_for_path(parent_path)
# If parent sensor has the property set, inherit it
if property_to_inherit in parent_config:
if property in parent_config:
path = fconf.get_path_for_id(config[CONF_ID])[:-1]
this_config = fconf.get_config_for_path(path)
this_config[property_to_inherit] = parent_config[property_to_inherit]
this_config = _walk_config(
fconf.get_config_for_path(path), property_path
)
value = parent_config[property]
if transform:
value = transform(value, config)
this_config[property] = value
return config

View File

@@ -2,6 +2,7 @@
#include "esphome/core/defines.h"
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
@@ -63,45 +64,6 @@ void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); }
uint32_t random_uint32() {
#ifdef USE_ESP32
return esp_random();
#elif defined(USE_ESP8266)
return os_random();
#endif
}
double random_double() { return random_uint32() / double(UINT32_MAX); }
float random_float() { return float(random_double()); }
void fill_random(uint8_t *data, size_t len) {
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
esp_fill_random(data, len);
#elif defined(USE_ESP8266)
int err = os_get_random(data, len);
assert(err == 0);
#else
#error "No random source for this system config"
#endif
}
static uint32_t fast_random_seed = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void fast_random_set_seed(uint32_t seed) { fast_random_seed = seed; }
uint32_t fast_random_32() {
fast_random_seed = (fast_random_seed * 2654435769ULL) + 40503ULL;
return fast_random_seed;
}
uint16_t fast_random_16() {
uint32_t rand32 = fast_random_32();
return (rand32 & 0xFFFF) + (rand32 >> 16);
}
uint8_t fast_random_8() {
uint32_t rand32 = fast_random_32();
return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF);
}
float gamma_correct(float value, float gamma) {
if (value <= 0.0f)
return 0.0f;
@@ -129,18 +91,6 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
return std::string(tmp);
}
std::string uint64_to_string(uint64_t num) {
char buffer[17];
auto *address16 = reinterpret_cast<uint16_t *>(&num);
snprintf(buffer, sizeof(buffer), "%04X%04X%04X%04X", address16[3], address16[2], address16[1], address16[0]);
return std::string(buffer);
}
std::string uint32_to_string(uint32_t num) {
char buffer[9];
auto *address16 = reinterpret_cast<uint16_t *>(&num);
snprintf(buffer, sizeof(buffer), "%04X%04X", address16[1], address16[0]);
return std::string(buffer);
}
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
if (on == nullptr && strcasecmp(str, "on") == 0)
@@ -187,63 +137,6 @@ void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trig
;
}
uint8_t reverse_bits_8(uint8_t x) {
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
return x;
}
uint16_t reverse_bits_16(uint16_t x) {
return uint16_t(reverse_bits_8(x & 0xFF) << 8) | uint16_t(reverse_bits_8(x >> 8));
}
std::string to_string(const std::string &val) { return val; }
std::string to_string(int val) {
char buf[64];
sprintf(buf, "%d", val);
return buf;
}
std::string to_string(long val) { // NOLINT
char buf[64];
sprintf(buf, "%ld", val);
return buf;
}
std::string to_string(long long val) { // NOLINT
char buf[64];
sprintf(buf, "%lld", val);
return buf;
}
std::string to_string(unsigned val) { // NOLINT
char buf[64];
sprintf(buf, "%u", val);
return buf;
}
std::string to_string(unsigned long val) { // NOLINT
char buf[64];
sprintf(buf, "%lu", val);
return buf;
}
std::string to_string(unsigned long long val) { // NOLINT
char buf[64];
sprintf(buf, "%llu", val);
return buf;
}
std::string to_string(float val) {
char buf[64];
sprintf(buf, "%f", val);
return buf;
}
std::string to_string(double val) {
char buf[64];
sprintf(buf, "%f", val);
return buf;
}
std::string to_string(long double val) {
char buf[64];
sprintf(buf, "%Lf", val);
return buf;
}
uint32_t fnv1_hash(const std::string &str) {
uint32_t hash = 2166136261UL;
for (char c : str) {
@@ -256,10 +149,6 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
return strcasecmp(a.c_str(), b.c_str()) == 0;
}
template<uint32_t> uint32_t reverse_bits(uint32_t x) {
return uint32_t(reverse_bits_16(x & 0xFFFF) << 16) | uint32_t(reverse_bits_16(x >> 16));
}
static int high_freq_num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void HighFrequencyLoopRequester::start() {
@@ -276,17 +165,6 @@ void HighFrequencyLoopRequester::stop() {
}
bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; }
template<typename T> T clamp(const T val, const T min, const T max) {
if (val < min)
return min;
if (val > max)
return max;
return val;
}
template uint8_t clamp(uint8_t, uint8_t, uint8_t);
template float clamp(float, float, float);
template int clamp(int, int, int);
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; }
@@ -397,6 +275,30 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
// ---------------------------------------------------------------------------------------------------------------------
// Mathematics
uint32_t random_uint32() {
#ifdef USE_ESP32
return esp_random();
#elif defined(USE_ESP8266)
return os_random();
#else
#error "No random source available for this configuration."
#endif
}
float random_float() { return static_cast<float>(random_uint32()) / static_cast<float>(UINT32_MAX); }
void random_bytes(uint8_t *data, size_t len) {
#ifdef USE_ESP32
esp_fill_random(data, len);
#elif defined(USE_ESP8266)
if (os_get_random(data, len) != 0) {
ESP_LOGE(TAG, "Failed to generate random bytes!");
}
#else
#error "No random source available for this configuration."
#endif
}
// Strings
std::string str_truncate(const std::string &str, size_t length) {
@@ -407,6 +309,16 @@ std::string str_until(const char *str, char ch) {
return pos == nullptr ? std::string(str) : std::string(str, pos - str);
}
std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
// wrapper around std::transform to run safely on functions from the ctype.h header
// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes
template<int (*fn)(int)> std::string str_ctype_transform(const std::string &str) {
std::string result;
result.resize(str.length());
std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); });
return result;
}
std::string str_lower_case(const std::string &str) { return str_ctype_transform<std::toupper>(str); }
std::string str_upper_case(const std::string &str) { return str_ctype_transform<std::tolower>(str); }
std::string str_snake_case(const std::string &str) {
std::string result;
result.resize(str.length());
@@ -451,7 +363,7 @@ std::string format_hex(const uint8_t *data, size_t length) {
}
return ret;
}
std::string format_hex(std::vector<uint8_t> data) { return format_hex(data.data(), data.size()); }
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
std::string format_hex_pretty(const uint8_t *data, size_t length) {
@@ -469,6 +381,6 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) {
return ret + " (" + to_string(length) + ")";
return ret;
}
std::string format_hex_pretty(std::vector<uint8_t> data) { return format_hex_pretty(data.data(), data.size()); }
std::string format_hex_pretty(const std::vector<uint8_t> &data) { return format_hex_pretty(data.data(), data.size()); }
} // namespace esphome

View File

@@ -9,8 +9,8 @@
#include <memory>
#include <type_traits>
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include "esp32-hal-psram.h"
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
#include "esphome/core/optional.h"
@@ -20,6 +20,14 @@
#define ALWAYS_INLINE __attribute__((always_inline))
#define PACKED __attribute__((packed))
// Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement).
// Define a substitute constexpr keyword for those functions, until we can drop C++11 support.
#if __cplusplus >= 201402L
#define constexpr14 constexpr
#else
#define constexpr14 inline // constexpr implies inline
#endif
namespace esphome {
/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
@@ -36,17 +44,6 @@ std::string get_mac_address_pretty();
void set_mac_address(uint8_t *mac);
#endif
std::string to_string(const std::string &val);
std::string to_string(int val);
std::string to_string(long val); // NOLINT
std::string to_string(long long val); // NOLINT
std::string to_string(unsigned val); // NOLINT
std::string to_string(unsigned long val); // NOLINT
std::string to_string(unsigned long long val); // NOLINT
std::string to_string(float val);
std::string to_string(double val);
std::string to_string(long double val);
/// Compare string a to string b (ignoring case) and return whether they are equal.
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
bool str_startswith(const std::string &full, const std::string &start);
@@ -69,15 +66,6 @@ class HighFrequencyLoopRequester {
bool started_{false};
};
/** Clamp the value between min and max.
*
* @param val The value.
* @param min The minimum value.
* @param max The maximum value.
* @return val clamped in between min and max.
*/
template<typename T> T clamp(T val, T min, T max);
/** Linearly interpolate between end start and end by completion.
*
* @tparam T The input/output typename.
@@ -98,25 +86,6 @@ template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args &&...
}
#endif
/// Return a random 32 bit unsigned integer.
uint32_t random_uint32();
/** Returns a random double between 0 and 1.
*
* Note: This function probably doesn't provide a truly uniform distribution.
*/
double random_double();
/// Returns a random float between 0 and 1. Essentially just casts random_double() to a float.
float random_float();
void fill_random(uint8_t *data, size_t len);
void fast_random_set_seed(uint32_t seed);
uint32_t fast_random_32();
uint16_t fast_random_16();
uint8_t fast_random_8();
/// Applies gamma correction with the provided gamma to value.
float gamma_correct(float value, float gamma);
/// Reverts gamma correction with the provided gamma to value.
@@ -125,21 +94,16 @@ float gamma_uncorrect(float value, float gamma);
/// Create a string from a value and an accuracy in decimals.
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
/// Convert a uint64_t to a hex string
std::string uint64_to_string(uint64_t num);
/// Convert a uint32_t to a hex string
std::string uint32_to_string(uint32_t num);
uint8_t reverse_bits_8(uint8_t x);
uint16_t reverse_bits_16(uint16_t x);
uint32_t reverse_bits_32(uint32_t x);
/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1)
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value);
/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1)
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue);
/// Convert degrees Celsius to degrees Fahrenheit.
static inline float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; }
/// Convert degrees Fahrenheit to degrees Celsius.
static inline float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; }
/***
* An interrupt helper class.
*
@@ -216,17 +180,6 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
std::vector<std::function<void(Ts...)>> callbacks_;
};
// https://stackoverflow.com/a/37161919/8924614
template<class T, class... Args>
struct is_callable // NOLINT
{
template<class U> static auto test(U *p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
static constexpr auto value = decltype(test<T>(nullptr))::value; // NOLINT
};
void delay_microseconds_safe(uint32_t us);
template<typename T> class Deduplicator {
@@ -261,31 +214,104 @@ template<typename T> class Parented {
uint32_t fnv1_hash(const std::string &str);
template<typename T> T *new_buffer(size_t length) {
T *buffer;
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
if (psramFound()) {
buffer = (T *) ps_malloc(length);
} else {
buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory)
}
#else
buffer = new T[length]; // NOLINT(cppcoreguidelines-owning-memory)
#endif
return buffer;
}
// ---------------------------------------------------------------------------------------------------------------------
/// @name STL backports
///@{
// std::byteswap is from C++23 and technically should be a template, but this will do for now.
constexpr uint8_t byteswap(uint8_t n) { return n; }
constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
// std::to_string() from C++11, available from libstdc++/g++ 8
// See https://github.com/espressif/esp-idf/issues/1445
#if _GLIBCXX_RELEASE >= 8
using std::to_string;
#else
inline std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT
inline std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT
inline std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT
inline std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT
inline std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT
inline std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT
inline std::string to_string(float value) { return str_snprintf("%f", 32, value); }
inline std::string to_string(double value) { return str_snprintf("%f", 32, value); }
inline std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); }
#endif
// std::is_trivially_copyable from C++11, implemented in libstdc++/g++ 5.1 (but minor releases can't be detected)
#if _GLIBCXX_RELEASE >= 6
using std::is_trivially_copyable;
#else
// Implementing this is impossible without compiler intrinsics, so don't bother. Invalid usage will be detected on
// other variants that use a newer compiler anyway.
// NOLINTNEXTLINE(readability-identifier-naming)
template<typename T> struct is_trivially_copyable : public std::integral_constant<bool, true> {};
#endif
// std::clamp from C++17
#if __cpp_lib_clamp >= 201603
using std::clamp;
#else
template<typename T, typename Compare> constexpr const T &clamp(const T &v, const T &lo, const T &hi, Compare comp) {
return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
}
template<typename T> constexpr const T &clamp(const T &v, const T &lo, const T &hi) {
return clamp(v, lo, hi, std::less<T>{});
}
#endif
// std::is_invocable from C++17
#if __cpp_lib_is_invocable >= 201703
using std::is_invocable;
#else
// https://stackoverflow.com/a/37161919/8924614
template<class T, class... Args> struct is_invocable { // NOLINT(readability-identifier-naming)
template<class U> static auto test(U *p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
static constexpr auto value = decltype(test<T>(nullptr))::value; // NOLINT
};
#endif
// std::bit_cast from C++20
#if __cpp_lib_bit_cast >= 201806
using std::bit_cast;
#else
/// Convert data between types, without aliasing issues or undefined behaviour.
template<
typename To, typename From,
enable_if_t<sizeof(To) == sizeof(From) && is_trivially_copyable<From>::value && is_trivially_copyable<To>::value,
int> = 0>
To bit_cast(const From &src) {
To dst;
memcpy(&dst, &src, sizeof(To));
return dst;
}
#endif
// std::byteswap from C++23
template<typename T> constexpr14 T byteswap(T n) {
T m;
for (size_t i = 0; i < sizeof(T); i++)
reinterpret_cast<uint8_t *>(&m)[i] = reinterpret_cast<uint8_t *>(&n)[sizeof(T) - 1 - i];
return m;
}
template<> constexpr14 uint8_t byteswap(uint8_t n) { return n; }
template<> constexpr14 uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
template<> constexpr14 uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
template<> constexpr14 uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
template<> constexpr14 int8_t byteswap(int8_t n) { return n; }
template<> constexpr14 int16_t byteswap(int16_t n) { return __builtin_bswap16(n); }
template<> constexpr14 int32_t byteswap(int32_t n) { return __builtin_bswap32(n); }
template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n); }
///@}
/// @name Mathematics
///@{
/// Return a random 32-bit unsigned integer.
uint32_t random_uint32();
/// Return a random float between 0 and 1.
float random_float();
/// Generate \p len number of random bytes.
void random_bytes(uint8_t *data, size_t len);
///@}
@@ -303,7 +329,8 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui
}
/// 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> inline T encode_value(const uint8_t *bytes) {
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
constexpr14 T encode_value(const uint8_t *bytes) {
T val = 0;
for (size_t i = 0; i < sizeof(T); i++) {
val <<= 8;
@@ -313,12 +340,12 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> inline T
}
/// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T).
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
inline T encode_value(const std::array<uint8_t, sizeof(T)> bytes) {
constexpr14 T encode_value(const std::array<uint8_t, sizeof(T)> bytes) {
return encode_value<T>(bytes.data());
}
/// Decode a value into its constituent bytes (from most to least significant).
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
inline std::array<uint8_t, sizeof(T)> decode_value(T val) {
constexpr14 std::array<uint8_t, sizeof(T)> decode_value(T val) {
std::array<uint8_t, sizeof(T)> ret{};
for (size_t i = sizeof(T); i > 0; i--) {
ret[i - 1] = val & 0xFF;
@@ -327,8 +354,25 @@ inline std::array<uint8_t, sizeof(T)> decode_value(T val) {
return ret;
}
/// Reverse the order of 8 bits.
inline uint8_t reverse_bits(uint8_t x) {
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
return x;
}
/// Reverse the order of 16 bits.
inline uint16_t reverse_bits(uint16_t x) {
return (reverse_bits(static_cast<uint8_t>(x & 0xFF)) << 8) | reverse_bits(static_cast<uint8_t>((x >> 8) & 0xFF));
}
/// Reverse the order of 32 bits.
inline uint32_t reverse_bits(uint32_t x) {
return (reverse_bits(static_cast<uint16_t>(x & 0xFFFF)) << 16) |
reverse_bits(static_cast<uint16_t>((x >> 16) & 0xFFFF));
}
/// Convert a value between host byte order and big endian (most significant byte first) order.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> constexpr T convert_big_endian(T val) {
template<typename T> constexpr14 T convert_big_endian(T val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return byteswap(val);
#else
@@ -336,11 +380,23 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> constexpr
#endif
}
/// Convert a value between host byte order and little endian (least significant byte first) order.
template<typename T> constexpr14 T convert_little_endian(T val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return val;
#else
return byteswap(val);
#endif
}
///@}
/// @name Strings
///@{
/// Convert the value to a string (added as extra overload so that to_string() can be used on all stringifiable types).
inline std::string to_string(const std::string &val) { return val; }
/// Truncate a string to a specific length.
std::string str_truncate(const std::string &str, size_t length);
@@ -350,6 +406,10 @@ std::string str_until(const char *str, char ch);
/// Extract the part of the string until either the first occurence of the specified character, or the end.
std::string str_until(const std::string &str, char ch);
/// Convert the string to lower case.
std::string str_lower_case(const std::string &str);
/// Convert the string to upper case.
std::string str_upper_case(const std::string &str);
/// Convert the string to snake case (lowercase with underscores).
std::string str_snake_case(const std::string &str);
@@ -457,7 +517,7 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
/// Format the byte array \p data of length \p len in lowercased hex.
std::string format_hex(const uint8_t *data, size_t length);
/// Format the vector \p data in lowercased hex.
std::string format_hex(std::vector<uint8_t> data);
std::string format_hex(const std::vector<uint8_t> &data);
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
val = convert_big_endian(val);
@@ -467,7 +527,7 @@ 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 vector \p data in pretty-printed, human-readable hex.
std::string format_hex_pretty(std::vector<uint8_t> data);
std::string format_hex_pretty(const std::vector<uint8_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);
@@ -480,12 +540,57 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri
///@{
/// Remap a number from one range to another.
template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max_out) {
template<typename T, typename U> constexpr T remap(U value, U min, U max, T min_out, T max_out) {
return (value - min) * (max_out - min_out) / (max - min) + min_out;
}
///@}
/// @name Memory management
///@{
/** An STL allocator that uses SPI RAM.
*
* By setting flags, it can be configured to don't try main memory if SPI RAM is full or unavailable, and to return
* `nulllptr` instead of aborting when no memory is available.
*/
template<class T> class ExternalRAMAllocator {
public:
using value_type = T;
enum Flags {
NONE = 0,
REFUSE_INTERNAL = 1 << 0, ///< Refuse falling back to internal memory when external RAM is full or unavailable.
ALLOW_FAILURE = 1 << 1, ///< Don't abort when memory allocation fails.
};
ExternalRAMAllocator() = default;
ExternalRAMAllocator(Flags flags) : flags_{flags} {}
template<class U> constexpr ExternalRAMAllocator(const ExternalRAMAllocator<U> &other) : flags_{other.flags} {}
T *allocate(size_t n) {
size_t size = n * sizeof(T);
T *ptr = nullptr;
#ifdef USE_ESP32
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM));
#endif
if (ptr == nullptr && (this->flags_ & Flags::REFUSE_INTERNAL) == 0)
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
if (ptr == nullptr && (this->flags_ & Flags::ALLOW_FAILURE) == 0)
abort();
return ptr;
}
void deallocate(T *p, size_t n) {
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
}
private:
Flags flags_{Flags::NONE};
};
/// @}
/// @name Deprecated functions
///@{

View File

@@ -33,7 +33,7 @@ namespace esphome {
#define ESPHOME_LOG_LEVEL_VERY_VERBOSE 7
#ifndef ESPHOME_LOG_LEVEL
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_DEBUG
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_NONE
#endif
#define ESPHOME_LOG_COLOR_BLACK "30"

View File

@@ -2,7 +2,8 @@
#include <cstring>
#include <cstdint>
#include <type_traits>
#include "esphome/core/helpers.h"
namespace esphome {
@@ -45,20 +46,12 @@ class ESPPreferences {
*/
virtual bool sync() = 0;
#ifndef USE_ESP8266
template<typename T, typename std::enable_if<std::is_trivially_copyable<T>::value, bool>::type = true>
#else
// esp8266 toolchain doesn't have is_trivially_copyable
template<typename T>
#endif
template<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
ESPPreferenceObject make_preference(uint32_t type, bool in_flash) {
return this->make_preference(sizeof(T), type, in_flash);
}
#ifndef USE_ESP8266
template<typename T, typename std::enable_if<std::is_trivially_copyable<T>::value, bool>::type = true>
#else
template<typename T>
#endif
template<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
ESPPreferenceObject make_preference(uint32_t type) {
return this->make_preference(sizeof(T), type);
}

Some files were not shown because too many files have changed in this diff Show More