Compare commits

...

157 Commits

Author SHA1 Message Date
Jesse Hills
5086cd716f Merge pull request #2203 from esphome/bump-2021.8.2
2021.8.2
2021-08-25 19:59:13 +12:00
Jesse Hills
4937af0cd9 Bump version to 2021.8.2 2021-08-25 19:46:55 +12:00
Jesse Hills
877a5fda41 Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202)
This reverts commit b0fa317302.
2021-08-25 19:46:54 +12:00
Jesse Hills
1fac91a659 Merge pull request #2199 from esphome/bump-2021.8.1
2021.8.1
2021-08-24 14:40:24 +12:00
Jesse Hills
0a4837c1f0 Bump version to 2021.8.1 2021-08-24 14:26:28 +12:00
mtl010957
e7404183a0 Internally all temperature units are Celsius so just send it directly (#1840) 2021-08-24 14:26:28 +12:00
Samuel Sieb
44f8dcfb6e Fix template select lambda (#2198) 2021-08-24 14:26:27 +12:00
Chris Nussbaum
481e0e98f8 Tuya fan component uses enum datapoint type for speed instead of integer (#2182)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-08-24 14:26:27 +12:00
puuu
9de40c26eb mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) 2021-08-24 14:26:27 +12:00
Oxan van Leeuwen
ad953f02d1 Fix addressable light control without transitions & effects with transitions (#2187) 2021-08-24 14:26:27 +12:00
puuu
3869e56521 Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) 2021-08-24 14:26:27 +12:00
Jesse Hills
63d87b17aa Fix pypi download url (#2177) 2021-08-24 14:26:27 +12:00
Jesse Hills
2e59ad90cc Fix docker release for new tags without v 2021-08-18 15:10:44 +12:00
Jesse Hills
1b8c9edcde Merge pull request #2176 from esphome/bump-2021.8.0
2021.8.0
2021-08-18 15:03:48 +12:00
Jesse Hills
d4c2a85f9c Bump version to v2021.8.0 2021-08-18 14:25:10 +12:00
Jesse Hills
8c75b87e94 Merge pull request #2175 from esphome/bump-1.21.0b3
1.21.0b3
2021-08-18 11:37:39 +12:00
Jesse Hills
409d4b9d47 Bump version to v1.21.0b3 2021-08-18 11:11:39 +12:00
Jesse Hills
4e3b95d120 Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) 2021-08-18 11:11:39 +12:00
Jesse Hills
61a9c9fa33 Remove specified accuracy_decimals from total_daily_energy (#2174) 2021-08-18 11:11:39 +12:00
Jesse Hills
9c605f2d46 Send dirty states when screen wakes up (#2167) 2021-08-18 11:11:39 +12:00
Franck Nijhof
44bb5a89c8 Add Gas device class to DSMR component (#2169) 2021-08-18 11:11:39 +12:00
Daniel Hyles
cbdb96f105 Add a dummy color temp (#2161) 2021-08-18 11:11:39 +12:00
Oxan van Leeuwen
9ee3463d07 Initialize color temperature to value within range if possible (#2168) 2021-08-18 11:11:39 +12:00
Jesse Hills
8bf0448f41 Merge pull request #2160 from esphome/bump-1.21.0b2
1.21.0b2
2021-08-16 14:22:28 +12:00
Jesse Hills
14e04eb231 Bump version to v1.21.0b2 2021-08-16 12:32:48 +12:00
Otto Winter
1be9bac3a9 Fix native API log level enum values (#2151) 2021-08-16 12:32:48 +12:00
Keith Burzinski
02b5a3efb8 Thermostat delayed fan mode fix (#2158) 2021-08-16 12:32:48 +12:00
puuu
bd457f64d8 let sensors announce its state_class via mqtt (#2155) 2021-08-16 12:32:48 +12:00
Oxan van Leeuwen
9efeea14f2 Always send all light state values in API (#2150) 2021-08-16 12:32:48 +12:00
Jesse Hills
d2cd65f5db Merge pull request #2144 from esphome/bump-1.21.0b1
1.21.0b1
2021-08-11 22:09:43 +12:00
Jesse Hills
2735f96516 Fix bad merge again 2021-08-11 21:54:24 +12:00
Jesse Hills
6847645782 Fix bad merge conflict 2021-08-11 21:45:04 +12:00
Jesse Hills
b0bc898278 Bump version to v1.21.0b1 2021-08-11 21:27:39 +12:00
Jesse Hills
c0f6af7213 Merge branch 'dev' into bump-1.21.0b1 2021-08-11 21:26:26 +12:00
Jesse Hills
5edebaf468 Bump version to v1.22.0-dev 2021-08-11 19:48:46 +12:00
Oxan van Leeuwen
d436409153 Support multiple configuration directories for update-all subcommand (#1925) 2021-08-11 17:21:57 +12:00
Branimir Lambov
8c41fc2b1d Support for the DKE screen version of LilyGo-TTGO-T5 V2.3 (#1969)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-11 17:14:17 +12:00
Oxan van Leeuwen
46f17bea66 Modular light transformers (#2124) 2021-08-11 16:51:35 +12:00
Stefan Agner
11477dbc03 Fix format warning in Tuya component (#1954) 2021-08-11 16:50:05 +12:00
buxtronix
947c104eff Support for AM43 BLE blind motors (#1744)
Co-authored-by: Ben Buxton <bb@cactii.net>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: René Klomp <rene@klomp.ws>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Geoff Davis <geoff@geoffdavis.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: dentra <dentra@users.noreply.github.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Barry Loong <loongyh@users.noreply.github.com>
Co-authored-by: Sergey V. DUDANOV <sergey.dudanov@gmail.com>
Co-authored-by: Balazs Scheidler <bazsi77@gmail.com>
2021-08-11 16:07:10 +12:00
Stefan Agner
e5366dbbe7 Add deassert_rts_dtr option to force RTS/DTR low when using miniterm (#2089) 2021-08-11 07:55:36 +12:00
Andreas Hergert
d3375193a9 Feature pipsolar anh (#1664)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-08-11 07:48:32 +12:00
Chris Nussbaum
6144ce1fe0 Break the Tuya set_datapoint_value method into separate methods per datapoint type (#2059)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
Co-authored-by: Trevor North <trevor@freedisc.co.uk>
2021-08-11 07:44:31 +12:00
Otto Winter
5f27757039 Merge pull request #2123 from esphome/bump-1.20.4
1.20.4
2021-08-04 17:54:53 +02:00
Otto winter
532907219b Bump version to v1.20.4 2021-08-04 17:46:10 +02:00
Otto Winter
eeaba74553 Fix external components not refreshing with default or high refresh time (#2122) 2021-08-04 17:46:10 +02:00
brambo123
dd637582a4 Fix time.on_time triggering if time jumped back (#1806)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-08-04 17:46:10 +02:00
Carlos Garcia Saura
b0d12aeea1 [duty_cycle] initialize two missing variables (#2088) 2021-08-04 17:46:09 +02:00
Łukasz Śliwiński
bdbd813455 Use proper schema for the analog pin shorthand (#2103)
The wrong error message is displayed like:
> GPIO17 (TOUT) is an analog-only pin on the ESP8266.
in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`.
2021-08-04 17:46:09 +02:00
Paul Monigatti
a6fac2b175 Fix min/max keys in MQTT Number to match Home Assistant (#2102) 2021-08-04 17:46:09 +02:00
Guillermo Ruffino
5ce923ea90 fix diplay trigger missing base class (#2099) 2021-08-04 17:46:08 +02:00
Otto Winter
29f0508dc2 Fix PID climate breaks when restoring old modes (#2086) 2021-08-04 17:46:08 +02:00
Otto Winter
3ffa59f0cd Fix climate restore schema changed resulting in invalid restore (#2068)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2021-08-04 17:46:08 +02:00
WeekendWarrior1
790d6ef94c Move configure_rmt() into setup() (#2028) 2021-08-04 17:46:08 +02:00
WeekendWarrior1
7828f48b9a Correctly invert esp32 RMT TX (#2022) 2021-08-04 17:46:07 +02:00
Jesse Hills
9fbb3659a6 Merge pull request #2098 from esphome/bump-1.20.3
1.20.3
2021-07-30 15:46:22 +12:00
Jesse Hills
fee446c28a Bump version to v1.20.3 2021-07-30 11:00:10 +12:00
Jesse Hills
1d56f0b035 Set pulse meter total to use state class measurement and last reset type auto (#2097) 2021-07-30 11:00:10 +12:00
Jesse Hills
341fddb9aa Merge pull request #2091 from esphome/bump-1.20.2
1.20.2
2021-07-29 20:42:05 +12:00
Jesse Hills
456824669f Bump version to v1.20.2 2021-07-29 19:51:17 +12:00
Jesse Hills
62f3039d82 Use sensor_schema for total_daily_energy (#2090)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-29 19:51:17 +12:00
Jesse Hills
be4c718859 HLW8012 - Dump energy sensor config (#2082) 2021-07-29 19:51:17 +12:00
Jesse Hills
c2f9ed7c59 Bump esphome dashboard to 20210728.0 (#2081) 2021-07-29 19:51:17 +12:00
John K. Luebs
bfac6607d1 More Tuya MCU robustness (#2080) 2021-07-29 19:51:17 +12:00
Jesse Hills
e43dcded62 Merge pull request #2074 from esphome/bump-1.20.1
1.20.1
2021-07-27 11:00:48 +12:00
Jesse Hills
887081fd71 Bump version to v1.20.1 2021-07-27 09:43:05 +12:00
Otto Winter
71ded24fce Fix MQTT climate custom fan modes without regular ones (#2071) 2021-07-27 09:43:05 +12:00
Chris Nussbaum
1e2a9e8348 Couple more updates for the Tuya component (#2065)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-07-27 09:43:05 +12:00
buxtronix
64a3aa7092 Log warning about lack of support for Anova nano (#2063)
Co-authored-by: Ben Buxton <bb@cactii.net>
2021-07-27 09:43:05 +12:00
carstenschroeder
fda8dd4ce3 Fixes new auto mode COOL and HEAT after #1994 (#2053) 2021-07-27 09:43:04 +12:00
Sergey V. DUDANOV
1efabd27d8 midea_ac: fix presets implementation (#2054) 2021-07-27 09:43:04 +12:00
Maurice Makaay
caa651e55b Accept change as proposed by black. (#2055) 2021-07-27 09:43:04 +12:00
Jesse Hills
10a6e9b4ee Merge pull request #2051 from esphome/bump-1.20.0
1.20.0
2021-07-22 08:32:30 +12:00
Jesse Hills
4b8ec44262 Bump version to v1.20.0 2021-07-22 07:55:49 +12:00
Jesse Hills
bd74ed4bc0 Merge branch 'beta' into bump-1.20.0 2021-07-22 07:55:49 +12:00
Jesse Hills
d01f296420 Merge pull request #2048 from esphome/bump-1.20.0b6
1.20.0b6
2021-07-21 11:35:27 +12:00
Jesse Hills
27112e2ace Bump version to v1.20.0b6 2021-07-21 10:52:48 +12:00
Sean Vig
837930234f Remove superfluous polling on ADS1115 (#2015) 2021-07-21 10:52:48 +12:00
Jesse Hills
e19aa3bbe0 Adding last_reset_type to sensors that should support it. (#2039) 2021-07-21 10:52:48 +12:00
Oxan van Leeuwen
35b5c1ed56 Fix white value transition for addressable lights (#2045) 2021-07-21 10:52:48 +12:00
Jesse Hills
c9d93ff685 Merge pull request #2044 from esphome/bump-1.20.0b5
1.20.0b5
2021-07-20 17:27:53 +12:00
Jesse Hills
fa72990a63 Bump version to v1.20.0b5 2021-07-20 17:09:58 +12:00
Otto Winter
e5afb1c4ea ESP32 ADC use esp-idf (#2024)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-20 17:09:57 +12:00
Sean Vig
73ead5f328 Correct ADS1115 handling of multiple sensors in continuous mode (#2016)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-07-20 17:09:57 +12:00
Paulus Schoutsen
5c57b51378 Bump dashboard to 20210719.0 (#2043) 2021-07-20 17:09:57 +12:00
Sergey V. DUDANOV
e25935ef21 midea_ac: Fix turbo mode. Preset BOOST. (#2029) 2021-07-20 17:09:57 +12:00
Jesse Hills
c7a52c3894 Add restore_value to template number (#2041) 2021-07-20 17:09:57 +12:00
Jesse Hills
53a4689ed1 Merge pull request #2040 from esphome/bump-1.20.0b4
1.20.0b4
2021-07-20 12:29:15 +12:00
Jesse Hills
0a82e6e792 Bump version to v1.20.0b4 2021-07-20 10:28:23 +12:00
Jesse Hills
98855e4123 Number and Template Number updates (#2036)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-07-20 10:28:22 +12:00
Jesse Hills
6a09d7c49b Merge pull request #2034 from esphome/bump-1.20.0b3
1.20.0b3
2021-07-19 08:44:04 +12:00
Jesse Hills
46e50ba53f Bump version to v1.20.0b3 2021-07-19 08:28:58 +12:00
Otto Winter
f1e3ff2ed2 Improve external components error messages (#2026) 2021-07-19 08:28:58 +12:00
Otto Winter
7787fa8f29 Dashboard disable assets caching (#2025) 2021-07-19 08:28:58 +12:00
Otto Winter
70902029f8 GH Actions CI use GHCR (#2027) 2021-07-19 08:28:58 +12:00
Otto Winter
4f9a56c884 Refactor docker build system and workflows (#2023) 2021-07-19 08:28:53 +12:00
Sean Vig
3715ba030b Fix ethernet component hostname handling (#2010)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-19 08:25:08 +12:00
Jesse Hills
0c93be97a9 Merge pull request #2017 from esphome/bump-1.20.0b2
1.20.0b2
2021-07-15 14:59:08 +12:00
Jesse Hills
54eb6070fb Bump version to v1.20.0b2 2021-07-15 13:55:58 +12:00
SenexCrenshaw
4dbf1c521e Nextion upload and sensors (#1464)
Co-authored-by: Senex Crenshaw <senexcrenshaw@gmail.com>
2021-07-15 13:55:58 +12:00
Jesse Hills
f30b8f6b3c Merge pull request #2014 from esphome/bump-1.20.0b1
1.20.0b1
2021-07-15 08:17:04 +12:00
Jesse Hills
18c08f24ad Bump version to v1.20.0b1 2021-07-15 07:45:05 +12:00
Jesse Hills
a7f53aea0e Merge branch 'dev' into bump-1.20.0b1 2021-07-15 07:45:05 +12:00
Jesse Hills
a91e6a6bdf Merge pull request #1959 from esphome/bump-1.19.4
1.19.4
2021-06-24 13:31:01 +12:00
Jesse Hills
8600620305 Bump version to v1.19.4 2021-06-24 12:49:45 +12:00
Jesse Hills
96721f305f Bump dashboard to 20210623.0 (#1958) 2021-06-24 12:49:45 +12:00
Otto Winter
2bf70d7d00 Compat argv parsing improvements (#1952) 2021-06-24 12:49:45 +12:00
Otto Winter
1d8c170f48 Add climate preset NONE again (#1951) 2021-06-24 12:49:45 +12:00
Otto Winter
6009c7edb4 Disallow power_save_mode NONE if used together with BLE (#1950) 2021-06-24 12:49:44 +12:00
Otto Winter
e3f36c033e API raise minor version for climate changes (#1947) 2021-06-24 12:49:44 +12:00
Otto Winter
d4eb0f1655 Rework climate traits (#1941)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-24 12:49:36 +12:00
Jesse Hills
e20ec00071 Merge pull request #1956 from esphome/bump-1.19.3
1.19.3
2021-06-23 20:16:42 +12:00
Jesse Hills
150114d774 Bump version to v1.19.3 2021-06-23 19:39:37 +12:00
Jesse Hills
89dfa5ea82 Bump esphome-dashboard to 20210622.0 (#1955) 2021-06-23 19:39:31 +12:00
Jesse Hills
97aa930ad2 Merge pull request #1943 from esphome/bump-1.19.2
1.19.2
2021-06-21 14:59:11 +12:00
Jesse Hills
2a5def10e7 Bump version to v1.19.2 2021-06-21 14:40:05 +12:00
Jesse Hills
969834e037 Fix bad climate control enum (#1942) 2021-06-21 14:40:05 +12:00
Jesse Hills
d73a44c504 Allow wifi setup to proceed when there is no sta or ap (#1931) 2021-06-21 14:40:05 +12:00
Sergey V. DUDANOV
8aec092ab6 Fix midea_ac query frame (#1940) 2021-06-21 14:40:05 +12:00
Chris Nussbaum
4fa959ba45 Don't send Tuya commands while currently receiving a message (#1886)
Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
2021-06-21 14:40:05 +12:00
Jesse Hills
b43712d78d Merge pull request #1936 from esphome/bump-1.19.1
1.19.1
2021-06-18 12:10:30 +12:00
Jesse Hills
01904a0f10 Bump version to v1.19.1 2021-06-18 11:52:02 +12:00
Jesse Hills
dd875e7529 Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) 2021-06-18 11:52:02 +12:00
Otto Winter
f1dcf0f0b8 Improve config final validation (#1917) 2021-06-18 11:52:02 +12:00
Sergey V. DUDANOV
a045d001bf Fix: midea_ac: fixed query status frame (#1922) 2021-06-18 11:52:01 +12:00
Paulus Schoutsen
066c1022d0 Update dashboard to 20210617.0 (#1930) 2021-06-18 11:52:01 +12:00
Jesse Hills
59c192becc Merge pull request #1923 from esphome/bump-1.19.0
1.19.0
2021-06-17 06:09:09 +12:00
Jesse Hills
a800816750 Merge branch 'beta' into bump-1.19.0 2021-06-17 05:59:02 +12:00
Jesse Hills
99d9ab4e40 Merge pull request #1926 from esphome/bump-1.19.0b7
1.19.0b7
2021-06-17 05:55:09 +12:00
Jesse Hills
f310ca1b74 Bump version to v1.19.0b7 2021-06-17 05:40:55 +12:00
Franck Nijhof
f763daa577 Fix update-all from dashboard (#1924) 2021-06-17 05:40:55 +12:00
Jesse Hills
970563e07b Bump version to v1.19.0 2021-06-16 21:00:51 +12:00
Jesse Hills
e006045f59 Merge pull request #1921 from esphome/bump-1.19.0b6
1.19.0b6
2021-06-16 15:42:43 +12:00
Jesse Hills
fff3645901 Bump version to v1.19.0b6 2021-06-16 13:51:31 +12:00
Jesse Hills
a5383fd208 Shorten the ble name to prevent crash with long device names (#1920) 2021-06-16 13:51:31 +12:00
Jesse Hills
9ce3a2059f Merge pull request #1919 from esphome/bump-1.19.0b5
1.19.0b5
2021-06-16 09:45:10 +12:00
Jesse Hills
69e6cf2c0c Bump version to v1.19.0b5 2021-06-16 09:06:50 +12:00
Paulus Schoutsen
28635124f9 Bump dashboard to 20210615.0 (#1918) 2021-06-16 09:06:50 +12:00
Jesse Hills
035be87a83 Merge pull request #1914 from esphome/bump-1.19.0b4
1.19.0b4
2021-06-15 20:47:38 +12:00
Jesse Hills
e8bdbc45a9 Bump version to v1.19.0b4 2021-06-15 20:26:53 +12:00
Guillermo Ruffino
429caccefa fixes compatibility with esphome cfg vscode (#1911) 2021-06-15 20:26:53 +12:00
Paulus Schoutsen
744ca1af7c Bump frontend to 20210614.0 (#1912) 2021-06-15 20:26:52 +12:00
Jesse Hills
106f0d611f Validate that either networks, ap, or improv is set up (#1910) 2021-06-15 20:26:52 +12:00
Jesse Hills
d826416684 Allow no networks or AP to be set. (#1908)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-06-15 20:26:52 +12:00
Jesse Hills
81959804df Merge pull request #1900 from esphome/bump-1.19.0b3
1.19.0b3
2021-06-12 11:15:03 +12:00
Jesse Hills
75b524ddc4 Fix formatting from cherry-pick conflict 2021-06-12 11:07:58 +12:00
Jesse Hills
f599c36272 Bump version to v1.19.0b3 2021-06-12 11:03:09 +12:00
Paulus Schoutsen
9bb64315f3 Add new wizard + allow installing firmware over webserial (#1887) 2021-06-12 11:03:09 +12:00
Jesse Hills
575badc690 Move esp32_ble_server to its own component (#1898) 2021-06-12 11:02:59 +12:00
Jesse Hills
4b91cfb7f9 Ensure wifi is in at least station mode before starting improv (#1899) 2021-06-12 11:01:54 +12:00
Jesse Hills
a57a842f7b Merge pull request #1888 from esphome/bump-1.19.0b2
1.19.0b2
2021-06-10 17:50:38 +12:00
Jesse Hills
a8c253a2a5 Bump version to v1.19.0b2 2021-06-10 17:17:52 +12:00
Geoff Davis
8b737aabd9 Add support for waveshare_epaper 1.54v2 (#1843)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-06-10 17:17:52 +12:00
Jesse Hills
0db4815f3d BLE loop use (#1882) 2021-06-10 17:17:52 +12:00
Oxan van Leeuwen
139db58a66 Simplify LightCall validation (#1874) 2021-06-10 17:17:52 +12:00
Jesse Hills
c32fec7432 Merge pull request #1883 from esphome/bump-1.19.0b1
1.19.0b1
2021-06-09 19:25:57 +12:00
Jesse Hills
8bd23dd457 Bump version to v1.19.0b1 2021-06-09 18:59:06 +12:00
70 changed files with 3354 additions and 275 deletions

View File

@@ -19,7 +19,7 @@ jobs:
id: tag
run: |
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
TAG="${GITHUB_REF#refs/tags/v}"
TAG="${GITHUB_REF#refs/tags/}"
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
@@ -138,7 +138,7 @@ jobs:
- env:
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/v}"
TAG="${GITHUB_REF#refs/tags/}"
curl \
-u ":$TOKEN" \
-X POST \

View File

@@ -14,6 +14,8 @@ esphome/core/* @esphome/core
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/api/* @OttoWinter
@@ -87,6 +89,7 @@ esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pmsa003i/* @sjtrny
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz

View File

@@ -11,6 +11,7 @@ from esphome.config import iter_components, read_config, strip_default_ids
from esphome.const import (
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
CONF_LOGGER,
CONF_OTA,
CONF_PASSWORD,
@@ -99,10 +100,21 @@ def run_miniterm(config, port):
baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
return
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False
with serial.Serial(port, baudrate=baud_rate) as ser:
ser = serial.Serial()
ser.baudrate = baud_rate
ser.port = port
# We can't set to False by default since it leads to toggling and hence
# ESP32 resets on some platforms.
if config["logger"][CONF_DEASSERT_RTS_DTR]:
ser.dtr = False
ser.rts = False
with ser:
while True:
try:
raw = ser.readline()
@@ -284,7 +296,6 @@ def command_vscode(args):
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
CORE.config_path = args.configuration
vscode.read_config(args)
@@ -394,7 +405,7 @@ def command_update_all(args):
import click
success = {}
files = list_yaml_files(args.configuration[0])
files = list_yaml_files(args.configuration)
twidth = 60
def print_bar(middle_text):
@@ -682,14 +693,12 @@ def parse_args(argv):
)
parser_vscode = subparsers.add_parser("vscode")
parser_vscode.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
parser_vscode.add_argument("--ace", action="store_true")
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directory.", nargs=1
"configuration", help="Your YAML configuration file directories.", nargs="+"
)
return parser.parse_args(argv[1:])

View File

View File

@@ -0,0 +1,115 @@
#include "am43.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace am43 {
static const char *TAG = "am43";
void Am43::dump_config() {
ESP_LOGCONFIG(TAG, "AM43");
LOG_SENSOR(" ", "Battery", this->battery_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
void Am43::setup() {
this->encoder_ = new Am43Encoder();
this->decoder_ = new Am43Decoder();
this->logged_in_ = false;
this->last_battery_update_ = 0;
this->current_sensor_ = 0;
}
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
this->logged_in_ = false;
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false;
this->node_state = espbt::ClientState::Idle;
if (this->battery_ != nullptr)
this->battery_->publish_state(NAN);
if (this->illuminance_ != nullptr)
this->illuminance_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
this->parent_->address_str().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
this->parent_->address_str().c_str());
}
break;
}
this->char_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
millis() - this->last_battery_update_ > 10000) {
this->battery_->publish_state(this->decoder_->battery_level_);
this->last_battery_update_ = millis();
}
if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
this->illuminance_->publish_state(this->decoder_->light_level_);
}
if (this->current_sensor_ > 0) {
if (this->illuminance_ != nullptr) {
auto packet = this->encoder_->get_light_level_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
this->current_sensor_ = 0;
}
break;
}
default:
break;
}
}
void Am43::update() {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->current_sensor_ == 0) {
if (this->battery_ != nullptr) {
auto packet = this->encoder_->get_battery_level_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
this->current_sensor_++;
}
}
} // namespace am43
} // namespace esphome
#endif

View File

@@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_battery(sensor::Sensor *battery) { battery_ = battery; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
protected:
uint16_t char_handle_;
Am43Encoder *encoder_;
Am43Decoder *decoder_;
bool logged_in_;
sensor::Sensor *battery_{nullptr};
sensor::Sensor *illuminance_{nullptr};
uint8_t current_sensor_;
// The AM43 often gets into a state where it spams loads of battery update
// notifications. Here we will limit to no more than every 10s.
uint8_t last_battery_update_;
};
} // namespace am43
} // namespace esphome
#endif

View File

@@ -0,0 +1,142 @@
#include "am43_base.h"
namespace esphome {
namespace am43 {
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
char buf[64];
memset(buf, 0, 64);
for (int i = 0; i < len; i++)
sprintf(&buf[i * 2], "%02x", data[i]);
std::string ret = buf;
return ret;
}
Am43Packet *Am43Encoder::get_battery_level_request() {
uint8_t data = 0x1;
return this->encode_(0xA2, &data, 1);
}
Am43Packet *Am43Encoder::get_light_level_request() {
uint8_t data = 0x1;
return this->encode_(0xAA, &data, 1);
}
Am43Packet *Am43Encoder::get_position_request() {
uint8_t data = 0x1;
return this->encode_(CMD_GET_POSITION, &data, 1);
}
Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
uint8_t data[2];
data[0] = (pin & 0xFF00) >> 8;
data[1] = pin & 0xFF;
return this->encode_(CMD_SEND_PIN, data, 2);
}
Am43Packet *Am43Encoder::get_open_request() {
uint8_t data = 0xDD;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_close_request() {
uint8_t data = 0xEE;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_stop_request() {
uint8_t data = 0xCC;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
return this->encode_(CMD_SET_POSITION, &position, 1);
}
void Am43Encoder::checksum_() {
uint8_t checksum = 0;
int i = 0;
for (i = 0; i < this->packet_.length; i++)
checksum = checksum ^ this->packet_.data[i];
this->packet_.data[i] = checksum ^ 0xff;
this->packet_.length++;
}
Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
memcpy(this->packet_.data, START_PACKET, 5);
this->packet_.data[5] = command;
this->packet_.data[6] = length;
memcpy(&this->packet_.data[7], data, length);
this->packet_.length = length + 7;
this->checksum_();
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
return &this->packet_;
}
#define VERIFY_MIN_LENGTH(x) \
if (length < (x)) \
return;
void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
this->has_battery_level_ = false;
this->has_light_level_ = false;
this->has_set_position_response_ = false;
this->has_set_state_response_ = false;
this->has_position_ = false;
this->has_pin_response_ = false;
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
if (length < 2 || data[0] != 0x9a)
return;
switch (data[1]) {
case CMD_GET_BATTERY_LEVEL: {
VERIFY_MIN_LENGTH(8);
this->battery_level_ = data[7];
this->has_battery_level_ = true;
break;
}
case CMD_GET_LIGHT_LEVEL: {
VERIFY_MIN_LENGTH(5);
this->light_level_ = 100 * ((float) data[4] / 9);
this->has_light_level_ = true;
break;
}
case CMD_GET_POSITION: {
VERIFY_MIN_LENGTH(6);
this->position_ = data[5];
this->has_position_ = true;
break;
}
case CMD_NOTIFY_POSITION: {
VERIFY_MIN_LENGTH(5);
this->position_ = data[4];
this->has_position_ = true;
break;
}
case CMD_SEND_PIN: {
VERIFY_MIN_LENGTH(4);
this->pin_ok_ = data[3] == RESPONSE_ACK;
this->has_pin_response_ = true;
break;
}
case CMD_SET_POSITION: {
VERIFY_MIN_LENGTH(4);
this->set_position_ok_ = data[3] == RESPONSE_ACK;
this->has_set_position_response_ = true;
break;
}
case CMD_SET_STATE: {
VERIFY_MIN_LENGTH(4);
this->set_state_ok_ = data[3] == RESPONSE_ACK;
this->has_set_state_response_ = true;
break;
}
default:
break;
}
};
} // namespace am43
} // namespace esphome

View File

@@ -0,0 +1,78 @@
#pragma once
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace am43 {
static const uint16_t AM43_SERVICE_UUID = 0xFE50;
static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
//
// Tuya identifiers, only to detect and warn users as they are incompatible.
static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
struct Am43Packet {
uint8_t length;
uint8_t data[24];
};
static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
static const uint8_t CMD_GET_POSITION = 0xA7;
static const uint8_t CMD_SEND_PIN = 0x17;
static const uint8_t CMD_SET_STATE = 0x0A;
static const uint8_t CMD_SET_POSITION = 0x0D;
static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
static const uint8_t RESPONSE_ACK = 0x5A;
static const uint8_t RESPONSE_NACK = 0xA5;
class Am43Encoder {
public:
Am43Packet *get_battery_level_request();
Am43Packet *get_light_level_request();
Am43Packet *get_position_request();
Am43Packet *get_send_pin_request(uint16_t pin);
Am43Packet *get_open_request();
Am43Packet *get_close_request();
Am43Packet *get_stop_request();
Am43Packet *get_set_position_request(uint8_t position);
protected:
void checksum_();
Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
Am43Packet packet_;
};
class Am43Decoder {
public:
void decode(const uint8_t *data, uint16_t length);
bool has_battery_level() { return this->has_battery_level_; }
bool has_light_level() { return this->has_light_level_; }
bool has_set_position_response() { return this->has_set_position_response_; }
bool has_set_state_response() { return this->has_set_state_response_; }
bool has_position() { return this->has_position_; }
bool has_pin_response() { return this->has_pin_response_; }
union {
uint8_t position_;
uint8_t battery_level_;
float light_level_;
uint8_t set_position_ok_;
uint8_t set_state_ok_;
uint8_t pin_ok_;
};
protected:
bool has_battery_level_;
bool has_light_level_;
bool has_set_position_response_;
bool has_set_state_response_;
bool has_position_;
bool has_pin_response_;
};
} // namespace am43
} // namespace esphome

View File

@@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import cover, ble_client
from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position"
am43_ns = cg.esphome_ns.namespace("am43")
Am43Component = am43_ns.class_(
"Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = (
cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Am43Component),
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config)

View File

@@ -0,0 +1,149 @@
#include "am43_cover.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace am43 {
static const char *TAG = "am43_cover";
using namespace esphome::cover;
void Am43Component::dump_config() {
LOG_COVER("", "AM43 Cover", this);
ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
}
void Am43Component::setup() {
this->position = COVER_OPEN;
this->encoder_ = new Am43Encoder();
this->decoder_ = new Am43Decoder();
this->logged_in_ = false;
}
void Am43Component::loop() {
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) {
auto packet = this->encoder_->get_send_pin_request(this->pin_);
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
else
this->logged_in_ = true;
}
}
CoverTraits Am43Component::get_traits() {
auto traits = CoverTraits();
traits.set_supports_position(true);
traits.set_supports_tilt(false);
traits.set_is_assumed_state(false);
return traits;
}
void Am43Component::control(const CoverCall &call) {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
return;
}
if (call.get_stop()) {
auto packet = this->encoder_->get_stop_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
if (this->invert_position_)
pos = 1 - pos;
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
}
}
void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false;
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str());
}
break;
}
this->char_handle_ = chr->handle;
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
}
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->decoder_->has_position()) {
this->position = ((float) this->decoder_->position_ / 100.0);
if (!this->invert_position_)
this->position = 1 - this->position;
if (this->position > 0.97)
this->position = 1.0;
if (this->position < 0.02)
this->position = 0.0;
this->publish_state();
}
if (this->decoder_->has_pin_response()) {
if (this->decoder_->pin_ok_) {
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
auto packet = this->encoder_->get_position_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
} else {
ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
}
}
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
break;
}
default:
break;
}
}
} // namespace am43
} // namespace esphome
#endif

View File

@@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/cover/cover.h"
#include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
public:
void setup() override;
void loop() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
cover::CoverTraits get_traits() override;
void set_pin(uint16_t pin) { this->pin_ = pin; }
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
protected:
void control(const cover::CoverCall &call) override;
uint16_t char_handle_;
uint16_t pin_;
bool invert_position_;
Am43Encoder *encoder_;
Am43Decoder *decoder_;
bool logged_in_;
float position_;
};
} // namespace am43
} // namespace esphome
#endif

View File

@@ -0,0 +1,46 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
ICON_BATTERY,
CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_PERCENT,
)
CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43")
Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_BATTERY, 0
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("120s"))
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))

View File

@@ -448,6 +448,7 @@ message LightCommandRequest {
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
STATE_CLASS_TOTAL_INCREASING = 2;
}
enum SensorLastResetType {
@@ -555,9 +556,10 @@ enum LogLevel {
LOG_LEVEL_ERROR = 1;
LOG_LEVEL_WARN = 2;
LOG_LEVEL_INFO = 3;
LOG_LEVEL_DEBUG = 4;
LOG_LEVEL_VERBOSE = 5;
LOG_LEVEL_VERY_VERBOSE = 6;
LOG_LEVEL_CONFIG = 4;
LOG_LEVEL_DEBUG = 5;
LOG_LEVEL_VERBOSE = 6;
LOG_LEVEL_VERY_VERBOSE = 7;
}
message SubscribeLogsRequest {
option (id) = 28;
@@ -572,7 +574,6 @@ message SubscribeLogsResponse {
option (no_delay) = false;
LogLevel level = 1;
string tag = 2;
string message = 3;
bool send_failed = 4;
}

View File

@@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) {
resp.key = light->get_object_id_hash();
resp.state = values.is_on();
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
if (color_mode & light::ColorCapability::BRIGHTNESS)
resp.brightness = values.get_brightness();
if (color_mode & light::ColorCapability::RGB) {
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
}
if (color_mode & light::ColorCapability::WHITE)
resp.white = values.get_white();
if (color_mode & light::ColorCapability::COLOR_TEMPERATURE)
resp.color_temperature = values.get_color_temperature();
if (color_mode & light::ColorCapability::COLD_WARM_WHITE) {
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
}
resp.brightness = values.get_brightness();
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
resp.white = values.get_white();
resp.color_temperature = values.get_color_temperature();
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects())
resp.effect = light->get_effect_name();
return this->send_light_state_response(resp);
@@ -701,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
auto buffer = this->create_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast<uint32_t>(level));
// string tag = 2;
// buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
// SubscribeLogsResponse - 29

View File

@@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
return "STATE_CLASS_NONE";
case enums::STATE_CLASS_MEASUREMENT:
return "STATE_CLASS_MEASUREMENT";
case enums::STATE_CLASS_TOTAL_INCREASING:
return "STATE_CLASS_TOTAL_INCREASING";
default:
return "UNKNOWN";
}
@@ -120,6 +122,8 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
return "LOG_LEVEL_WARN";
case enums::LOG_LEVEL_INFO:
return "LOG_LEVEL_INFO";
case enums::LOG_LEVEL_CONFIG:
return "LOG_LEVEL_CONFIG";
case enums::LOG_LEVEL_DEBUG:
return "LOG_LEVEL_DEBUG";
case enums::LOG_LEVEL_VERBOSE:
@@ -2334,10 +2338,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->tag = value.as_string();
return true;
}
case 3: {
this->message = value.as_string();
return true;
@@ -2348,7 +2348,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite
}
void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::LogLevel>(1, this->level);
buffer.encode_string(2, this->tag);
buffer.encode_string(3, this->message);
buffer.encode_bool(4, this->send_failed);
}
@@ -2360,10 +2359,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
out.append(proto_enum_to_string<enums::LogLevel>(this->level));
out.append("\n");
out.append(" tag: ");
out.append("'").append(this->tag).append("'");
out.append("\n");
out.append(" message: ");
out.append("'").append(this->message).append("'");
out.append("\n");

View File

@@ -47,6 +47,7 @@ enum ColorMode : uint32_t {
enum SensorStateClass : uint32_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
STATE_CLASS_TOTAL_INCREASING = 2,
};
enum SensorLastResetType : uint32_t {
LAST_RESET_NONE = 0,
@@ -58,9 +59,10 @@ enum LogLevel : uint32_t {
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_VERBOSE = 5,
LOG_LEVEL_VERY_VERBOSE = 6,
LOG_LEVEL_CONFIG = 4,
LOG_LEVEL_DEBUG = 5,
LOG_LEVEL_VERBOSE = 6,
LOG_LEVEL_VERY_VERBOSE = 7,
};
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
@@ -627,7 +629,6 @@ class SubscribeLogsRequest : public ProtoMessage {
class SubscribeLogsResponse : public ProtoMessage {
public:
enums::LogLevel level{};
std::string tag{};
std::string message{};
bool send_failed{false};
void encode(ProtoWriteBuffer buffer) const override;

View File

@@ -25,7 +25,7 @@ class BLEClientNode {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
virtual void loop() = 0;
virtual void loop(){};
void set_address(uint64_t address) { address_ = address; }
espbt::ESPBTClient *client;
// This should be transitioned to Established once the node no longer needs

View File

@@ -5,6 +5,7 @@ from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
@@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema(
"",
ICON_EMPTY,
3,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_GAS,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_NEVER,
),
@@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema(
"",
ICON_EMPTY,
3,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_GAS,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_NEVER,
),

View File

@@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE});
traits.set_min_mireds(153);
traits.set_max_mireds(500);
return traits;
}

View File

@@ -24,6 +24,10 @@ void AddressableLight::call_setup() {
#endif
}
std::unique_ptr<LightTransformer> AddressableLight::create_default_transition() {
return make_unique<AddressableLightTransformer>(*this);
}
Color esp_color_from_light_color_values(LightColorValues val) {
auto r = to_uint8_scale(val.get_color_brightness() * val.get_red());
auto g = to_uint8_scale(val.get_color_brightness() * val.get_green());
@@ -37,66 +41,72 @@ void AddressableLight::write_state(LightState *state) {
auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state());
this->correction_.set_local_brightness(max_brightness);
this->last_transition_progress_ = 0.0f;
this->accumulated_alpha_ = 0.0f;
if (this->is_effect_active())
return;
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
this->all() = esp_color_from_light_color_values(val);
this->schedule_show();
}
if (state->transformer_ == nullptr || !state->transformer_->is_transition()) {
// no transformer active or non-transition one
this->all() = esp_color_from_light_color_values(val);
} else {
// transition transformer active, activate specialized transition for addressable effects
// instead of using a unified transition for all LEDs, we use the current state each LED as the
// start. Warning: ugly
void AddressableLightTransformer::start() {
// don't try to transition over running effects.
if (this->light_.is_effect_active())
return;
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
// state of each LED at the start of the transition
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
// dynamically-calculated alpha values to match the look of the
auto end_values = this->target_values_;
this->target_color_ = esp_color_from_light_color_values(end_values);
float new_progress = state->transformer_->get_progress();
float prev_smoothed = LightTransitionTransformer::smoothed_progress(last_transition_progress_);
float new_smoothed = LightTransitionTransformer::smoothed_progress(new_progress);
this->last_transition_progress_ = new_progress;
// our transition will handle brightness, disable brightness in correction.
this->light_.correction_.set_local_brightness(255);
this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
}
auto end_values = state->transformer_->get_end_values();
Color target_color = esp_color_from_light_color_values(end_values);
optional<LightColorValues> AddressableLightTransformer::apply() {
// Don't try to transition over running effects, instead immediately use the target values. write_state() and the
// effects pick up the change from current_values.
if (this->light_.is_effect_active())
return this->target_values_;
// our transition will handle brightness, disable brightness in correction.
this->correction_.set_local_brightness(255);
target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
// Use a specialized transition for addressable lights: instead of using a unified transition for
// all LEDs, we use the current state of each LED as the start.
float denom = (1.0f - new_smoothed);
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
// state of each LED at the start of the transition.
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
// dynamically-calculated alpha values to match the look.
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
// We solve this by accumulating the fractional part of the alpha over time.
float alpha255 = alpha * 255.0f;
float alpha255int = floorf(alpha255);
float alpha255remainder = alpha255 - alpha255int;
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
this->accumulated_alpha_ += alpha255remainder;
float alpha_add = floorf(this->accumulated_alpha_);
this->accumulated_alpha_ -= alpha_add;
float denom = (1.0f - smoothed_progress);
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
alpha255 += alpha_add;
alpha255 = clamp(alpha255, 0.0f, 255.0f);
auto alpha8 = static_cast<uint8_t>(alpha255);
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
// We solve this by accumulating the fractional part of the alpha over time.
float alpha255 = alpha * 255.0f;
float alpha255int = floorf(alpha255);
float alpha255remainder = alpha255 - alpha255int;
if (alpha8 != 0) {
uint8_t inv_alpha8 = 255 - alpha8;
Color add = target_color * alpha8;
this->accumulated_alpha_ += alpha255remainder;
float alpha_add = floorf(this->accumulated_alpha_);
this->accumulated_alpha_ -= alpha_add;
for (auto led : *this)
led = add + led.get() * inv_alpha8;
}
alpha255 += alpha_add;
alpha255 = clamp(alpha255, 0.0f, 255.0f);
auto alpha8 = static_cast<uint8_t>(alpha255);
if (alpha8 != 0) {
uint8_t inv_alpha8 = 255 - alpha8;
Color add = this->target_color_ * alpha8;
for (auto led : this->light_)
led.set(add + led.get() * inv_alpha8);
}
this->schedule_show();
this->last_transition_progress_ = smoothed_progress;
this->light_.schedule_show();
return {};
}
} // namespace light

View File

@@ -8,6 +8,7 @@
#include "esp_range_view.h"
#include "light_output.h"
#include "light_state.h"
#include "transformers.h"
#ifdef USE_POWER_SUPPLY
#include "esphome/components/power_supply/power_supply.h"
@@ -53,6 +54,7 @@ class AddressableLight : public LightOutput, public Component {
bool is_effect_active() const { return this->effect_active_; }
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
void write_state(LightState *state) override;
std::unique_ptr<LightTransformer> create_default_transition() override;
void set_correction(float red, float green, float blue, float white = 1.0f) {
this->correction_.set_max_brightness(
Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white)));
@@ -70,6 +72,8 @@ class AddressableLight : public LightOutput, public Component {
void call_setup() override;
protected:
friend class AddressableLightTransformer;
bool should_show_() const { return this->effect_active_ || this->next_show_; }
void mark_shown_() {
this->next_show_ = false;
@@ -92,6 +96,18 @@ class AddressableLight : public LightOutput, public Component {
power_supply::PowerSupplyRequester power_;
#endif
LightState *state_parent_{nullptr};
};
class AddressableLightTransformer : public LightTransitionTransformer {
public:
AddressableLightTransformer(AddressableLight &light) : light_(light) {}
void start() override;
optional<LightColorValues> apply() override;
protected:
AddressableLight &light_;
Color target_color_{};
float last_transition_progress_{0.0f};
float accumulated_alpha_{0.0f};
};

View File

@@ -0,0 +1,12 @@
#include "light_output.h"
#include "transformers.h"
namespace esphome {
namespace light {
std::unique_ptr<LightTransformer> LightOutput::create_default_transition() {
return make_unique<LightTransitionTransformer>();
}
} // namespace light
} // namespace esphome

View File

@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "light_traits.h"
#include "light_state.h"
#include "light_transformer.h"
namespace esphome {
namespace light {
@@ -13,6 +14,9 @@ class LightOutput {
/// Return the LightTraits of this LightOutput.
virtual LightTraits get_traits() = 0;
/// Return the default transformer used for transitions.
virtual std::unique_ptr<LightTransformer> create_default_transition();
virtual void setup_state(LightState *state) {}
virtual void write_state(LightState *state) = 0;

View File

@@ -1,6 +1,7 @@
#include "light_state.h"
#include "esphome/core/log.h"
#include "light_state.h"
#include "light_output.h"
#include "transformers.h"
namespace esphome {
namespace light {
@@ -38,6 +39,13 @@ void LightState::setup() {
effect->init_internal(this);
}
// When supported color temperature range is known, initialize color temperature setting within bounds.
float min_mireds = this->get_traits().get_min_mireds();
if (min_mireds > 0) {
this->remote_values.set_color_temperature(min_mireds);
this->current_values.set_color_temperature(min_mireds);
}
auto call = this->make_call();
LightStateRTCState recovered{};
switch (this->restore_mode_) {
@@ -105,19 +113,19 @@ void LightState::loop() {
// Apply transformer (if any)
if (this->transformer_ != nullptr) {
auto values = this->transformer_->apply();
this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to
if (values.has_value())
this->current_values = *values;
if (this->transformer_->is_finished()) {
this->remote_values = this->current_values = this->transformer_->get_end_values();
this->target_state_reached_callback_.call();
if (this->transformer_->publish_at_end())
this->publish_state();
this->transformer_->stop();
this->transformer_ = nullptr;
} else {
this->current_values = this->transformer_->get_values();
this->remote_values = this->transformer_->get_remote_values();
this->target_state_reached_callback_.call();
}
this->next_write_ = true;
}
// Write state to the light
if (this->next_write_) {
this->output_->write_state(this);
this->next_write_ = false;
@@ -217,18 +225,21 @@ void LightState::stop_effect_() {
}
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
this->remote_values = this->transformer_->get_remote_values();
this->transformer_ = this->output_->create_default_transition();
this->transformer_->setup(this->current_values, target, length);
this->remote_values = target;
}
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
LightColorValues end_colors = this->current_values;
LightColorValues end_colors = this->remote_values;
// If starting a flash if one is already happening, set end values to end values of current flash
// Hacky but works
if (this->transformer_ != nullptr)
end_colors = this->transformer_->get_end_values();
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
this->remote_values = this->transformer_->get_remote_values();
end_colors = this->transformer_->get_target_values();
this->transformer_ = make_unique<LightFlashTransformer>(*this);
this->transformer_->setup(end_colors, target, length);
this->remote_values = target;
}
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
@@ -240,10 +251,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
this->next_write_ = true;
}
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
this->transformer_ = std::move(transformer);
}
void LightState::save_remote_values_() {
LightStateRTCState saved;
saved.color_mode = this->remote_values.get_color_mode();

View File

@@ -157,9 +157,6 @@ class LightState : public Nameable, public Component {
/// Internal method to set the color values to target immediately (with no transition).
void set_immediately_(const LightColorValues &target, bool set_remote_values);
/// Internal method to start a transformer.
void set_transformer_(std::unique_ptr<LightTransformer> transformer);
/// Internal method to save the current remote_values to the preferences
void save_remote_values_();

View File

@@ -1,42 +1,42 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "light_color_values.h"
namespace esphome {
namespace light {
/// Base-class for all light color transformers, such as transitions or flashes.
/// Base class for all light color transformers, such as transitions or flashes.
class LightTransformer {
public:
LightTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
const LightColorValues &target_values)
: start_time_(start_time), length_(length), start_values_(start_values), target_values_(target_values) {}
void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) {
this->start_time_ = millis();
this->length_ = length;
this->start_values_ = start_values;
this->target_values_ = target_values;
this->start();
}
LightTransformer() = delete;
/// Indicates whether this transformation is finished.
virtual bool is_finished() { return this->get_progress_() >= 1.0f; }
/// Whether this transformation is finished
virtual bool is_finished() { return this->get_progress() >= 1.0f; }
/// This will be called before the transition is started.
virtual void start() {}
/// This will be called to get the current values for output.
virtual LightColorValues get_values() = 0;
/// This will be called while the transformer is active to apply the transition to the light. Can either write to the
/// light directly, or return LightColorValues that will be applied.
virtual optional<LightColorValues> apply() = 0;
/// The values that should be reported to the front-end.
virtual LightColorValues get_remote_values() { return this->get_target_values_(); }
/// This will be called after transition is finished.
virtual void stop() {}
/// The values that should be set after this transformation is complete.
virtual LightColorValues get_end_values() { return this->get_target_values_(); }
const LightColorValues &get_start_values() const { return this->start_values_; }
virtual bool publish_at_end() = 0;
virtual bool is_transition() = 0;
float get_progress() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
const LightColorValues &get_target_values() const { return this->target_values_; }
protected:
const LightColorValues &get_start_values_() const { return this->start_values_; }
const LightColorValues &get_target_values_() const { return this->target_values_; }
/// The progress of this transition, on a scale of 0 to 1.
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
uint32_t start_time_;
uint32_t length_;
@@ -44,69 +44,5 @@ class LightTransformer {
LightColorValues target_values_;
};
class LightTransitionTransformer : public LightTransformer {
public:
LightTransitionTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
const LightColorValues &target_values)
: LightTransformer(start_time, length, start_values, target_values) {
// When turning light on from off state, use colors from new.
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
this->start_values_ = LightColorValues(target_values);
this->start_values_.set_brightness(0.0f);
}
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
this->changing_color_mode_ = true;
this->intermediate_values_ = LightColorValues(this->get_start_values_());
this->intermediate_values_.set_state(false);
}
}
LightColorValues get_values() override {
float p = this->get_progress();
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
if (this->changing_color_mode_ && p > 0.5f &&
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
this->intermediate_values_ = LightColorValues(this->get_end_values());
this->intermediate_values_.set_state(false);
}
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
if (this->changing_color_mode_)
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
float v = LightTransitionTransformer::smoothed_progress(p);
return LightColorValues::lerp(start, end, v);
}
bool publish_at_end() override { return false; }
bool is_transition() override { return true; }
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
// transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
protected:
bool changing_color_mode_{false};
LightColorValues intermediate_values_{};
};
class LightFlashTransformer : public LightTransformer {
public:
LightFlashTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
const LightColorValues &target_values)
: LightTransformer(start_time, length, start_values, target_values) {}
LightColorValues get_values() override { return this->get_target_values_(); }
LightColorValues get_end_values() override { return this->get_start_values_(); }
bool publish_at_end() override { return true; }
bool is_transition() override { return false; }
};
} // namespace light
} // namespace esphome

View File

@@ -0,0 +1,75 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "light_color_values.h"
#include "light_state.h"
#include "light_transformer.h"
namespace esphome {
namespace light {
class LightTransitionTransformer : public LightTransformer {
public:
void start() override {
// When turning light on from off state, use colors from target state.
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
this->start_values_ = LightColorValues(this->target_values_);
this->start_values_.set_brightness(0.0f);
}
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
this->changing_color_mode_ = true;
this->intermediate_values_ = this->start_values_;
this->intermediate_values_.set_state(false);
}
}
optional<LightColorValues> apply() override {
float p = this->get_progress_();
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
if (this->changing_color_mode_ && p > 0.5f &&
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
this->intermediate_values_ = this->target_values_;
this->intermediate_values_.set_state(false);
}
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
if (this->changing_color_mode_)
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
float v = LightTransitionTransformer::smoothed_progress(p);
return LightColorValues::lerp(start, end, v);
}
protected:
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
// transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
bool changing_color_mode_{false};
LightColorValues intermediate_values_{};
};
class LightFlashTransformer : public LightTransformer {
public:
LightFlashTransformer(LightState &state) : state_(state) {}
optional<LightColorValues> apply() override { return this->get_target_values(); }
// Restore the original values after the flash.
void stop() override {
this->state_.current_values = this->get_start_values();
this->state_.remote_values = this->get_start_values();
this->state_.publish_state();
}
protected:
LightState &state_;
};
} // namespace light
} // namespace esphome

View File

@@ -7,6 +7,7 @@ from esphome.automation import LambdaAction
from esphome.const import (
CONF_ARGS,
CONF_BAUD_RATE,
CONF_DEASSERT_RTS_DTR,
CONF_FORMAT,
CONF_HARDWARE_UART,
CONF_ID,
@@ -104,6 +105,7 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(Logger),
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection,
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
cv.Optional(CONF_LOGS, default={}): cv.Schema(

View File

@@ -6,6 +6,9 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/midea_dongle/midea_dongle.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/midea_dongle/midea_dongle.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "midea_frame.h"
namespace esphome {

View File

@@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
root["max_temp"] = traits.get_visual_max_temperature();
// temp_step
root["temp_step"] = traits.get_visual_temperature_step();
// temperature units are always coerced to Celsius internally
root["temp_unit"] = "C";
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
// away_mode_command_topic

View File

@@ -54,12 +54,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
// legacy API
if (traits.supports_color_capability(ColorCapability::BRIGHTNESS))
root["brightness"] = true;
if (traits.supports_color_capability(ColorCapability::RGB))
root["rgb"] = true;
if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE))
root["color_temp"] = true;
if (traits.supports_color_capability(ColorCapability::WHITE))
root["white_value"] = true;
if (this->state_->supports_effects()) {
root["effect"] = true;

View File

@@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo
if (this->sensor_->get_force_update())
root["force_update"] = true;
if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT)
root["state_class"] = "measurement";
config.command_topic = false;
}
bool MQTTSensorComponent::send_initial_state() {

View File

@@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() {
ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically");
this->is_sleeping_ = false;
this->wake_callback_.call();
this->all_components_send_state_(false);
break;
}
case 0x88: // system successful start up

View File

@@ -0,0 +1,32 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.components import uart
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@andreashergert1984"]
AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "output"]
MULTI_CONF = True
CONF_PIPSOLAR_ID = "pipsolar_id"
pipsolar_ns = cg.esphome_ns.namespace("pipsolar")
PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component)
PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend(
{
cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema({cv.GenerateID(): cv.declare_id(PipsolarComponent)})
.extend(cv.polling_component_schema("1s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)

View File

@@ -0,0 +1,144 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
CONF_ID,
)
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID
DEPENDENCIES = ["uart"]
CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version"
CONF_CONFIGURATION_STATUS = "configuration_status"
CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version"
CONF_LOAD_STATUS = "load_status"
CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING = (
"battery_voltage_to_steady_while_charging"
)
CONF_CHARGING_STATUS = "charging_status"
CONF_SCC_CHARGING_STATUS = "scc_charging_status"
CONF_AC_CHARGING_STATUS = "ac_charging_status"
CONF_CHARGING_TO_FLOATING_MODE = "charging_to_floating_mode"
CONF_SWITCH_ON = "switch_on"
CONF_DUSTPROOF_INSTALLED = "dustproof_installed"
CONF_SILENCE_BUZZER_OPEN_BUZZER = "silence_buzzer_open_buzzer"
CONF_OVERLOAD_BYPASS_FUNCTION = "overload_bypass_function"
CONF_LCD_ESCAPE_TO_DEFAULT = "lcd_escape_to_default"
CONF_OVERLOAD_RESTART_FUNCTION = "overload_restart_function"
CONF_OVER_TEMPERATURE_RESTART_FUNCTION = "over_temperature_restart_function"
CONF_BACKLIGHT_ON = "backlight_on"
CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT = "alarm_on_when_primary_source_interrupt"
CONF_FAULT_CODE_RECORD = "fault_code_record"
CONF_POWER_SAVING = "power_saving"
CONF_WARNINGS_PRESENT = "warnings_present"
CONF_FAULTS_PRESENT = "faults_present"
CONF_WARNING_POWER_LOSS = "warning_power_loss"
CONF_FAULT_INVERTER_FAULT = "fault_inverter_fault"
CONF_FAULT_BUS_OVER = "fault_bus_over"
CONF_FAULT_BUS_UNDER = "fault_bus_under"
CONF_FAULT_BUS_SOFT_FAIL = "fault_bus_soft_fail"
CONF_WARNING_LINE_FAIL = "warning_line_fail"
CONF_FAULT_OPVSHORT = "fault_opvshort"
CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW = "fault_inverter_voltage_too_low"
CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH = "fault_inverter_voltage_too_high"
CONF_WARNING_OVER_TEMPERATURE = "warning_over_temperature"
CONF_WARNING_FAN_LOCK = "warning_fan_lock"
CONF_WARNING_BATTERY_VOLTAGE_HIGH = "warning_battery_voltage_high"
CONF_WARNING_BATTERY_LOW_ALARM = "warning_battery_low_alarm"
CONF_WARNING_BATTERY_UNDER_SHUTDOWN = "warning_battery_under_shutdown"
CONF_WARNING_BATTERY_DERATING = "warning_battery_derating"
CONF_WARNING_OVER_LOAD = "warning_over_load"
CONF_WARNING_EEPROM_FAILED = "warning_eeprom_failed"
CONF_FAULT_INVERTER_OVER_CURRENT = "fault_inverter_over_current"
CONF_FAULT_INVERTER_SOFT_FAILED = "fault_inverter_soft_failed"
CONF_FAULT_SELF_TEST_FAILED = "fault_self_test_failed"
CONF_FAULT_OP_DC_VOLTAGE_OVER = "fault_op_dc_voltage_over"
CONF_FAULT_BATTERY_OPEN = "fault_battery_open"
CONF_FAULT_CURRENT_SENSOR_FAILED = "fault_current_sensor_failed"
CONF_FAULT_BATTERY_SHORT = "fault_battery_short"
CONF_WARNING_POWER_LIMIT = "warning_power_limit"
CONF_WARNING_PV_VOLTAGE_HIGH = "warning_pv_voltage_high"
CONF_FAULT_MPPT_OVERLOAD = "fault_mppt_overload"
CONF_WARNING_MPPT_OVERLOAD = "warning_mppt_overload"
CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE = "warning_battery_too_low_to_charge"
CONF_FAULT_DC_DC_OVER_CURRENT = "fault_dc_dc_over_current"
CONF_FAULT_CODE = "fault_code"
CONF_WARNUNG_LOW_PV_ENERGY = "warnung_low_pv_energy"
CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START = (
"warning_high_ac_input_during_bus_soft_start"
)
CONF_WARNING_BATTERY_EQUALIZATION = "warning_battery_equalization"
TYPES = [
CONF_ADD_SBU_PRIORITY_VERSION,
CONF_CONFIGURATION_STATUS,
CONF_SCC_FIRMWARE_VERSION,
CONF_LOAD_STATUS,
CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING,
CONF_CHARGING_STATUS,
CONF_SCC_CHARGING_STATUS,
CONF_AC_CHARGING_STATUS,
CONF_CHARGING_TO_FLOATING_MODE,
CONF_SWITCH_ON,
CONF_DUSTPROOF_INSTALLED,
CONF_SILENCE_BUZZER_OPEN_BUZZER,
CONF_OVERLOAD_BYPASS_FUNCTION,
CONF_LCD_ESCAPE_TO_DEFAULT,
CONF_OVERLOAD_RESTART_FUNCTION,
CONF_OVER_TEMPERATURE_RESTART_FUNCTION,
CONF_BACKLIGHT_ON,
CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT,
CONF_FAULT_CODE_RECORD,
CONF_POWER_SAVING,
CONF_WARNINGS_PRESENT,
CONF_FAULTS_PRESENT,
CONF_WARNING_POWER_LOSS,
CONF_FAULT_INVERTER_FAULT,
CONF_FAULT_BUS_OVER,
CONF_FAULT_BUS_UNDER,
CONF_FAULT_BUS_SOFT_FAIL,
CONF_WARNING_LINE_FAIL,
CONF_FAULT_OPVSHORT,
CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW,
CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH,
CONF_WARNING_OVER_TEMPERATURE,
CONF_WARNING_FAN_LOCK,
CONF_WARNING_BATTERY_VOLTAGE_HIGH,
CONF_WARNING_BATTERY_LOW_ALARM,
CONF_WARNING_BATTERY_UNDER_SHUTDOWN,
CONF_WARNING_BATTERY_DERATING,
CONF_WARNING_OVER_LOAD,
CONF_WARNING_EEPROM_FAILED,
CONF_FAULT_INVERTER_OVER_CURRENT,
CONF_FAULT_INVERTER_SOFT_FAILED,
CONF_FAULT_SELF_TEST_FAILED,
CONF_FAULT_OP_DC_VOLTAGE_OVER,
CONF_FAULT_BATTERY_OPEN,
CONF_FAULT_CURRENT_SENSOR_FAILED,
CONF_FAULT_BATTERY_SHORT,
CONF_WARNING_POWER_LIMIT,
CONF_WARNING_PV_VOLTAGE_HIGH,
CONF_FAULT_MPPT_OVERLOAD,
CONF_WARNING_MPPT_OVERLOAD,
CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE,
CONF_FAULT_DC_DC_OVER_CURRENT,
CONF_FAULT_CODE,
CONF_WARNUNG_LOW_PV_ENERGY,
CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START,
CONF_WARNING_BATTERY_EQUALIZATION,
]
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type in TYPES:
if type in config:
conf = config[type]
sens = cg.new_Pvariable(conf[CONF_ID])
await binary_sensor.register_binary_sensor(sens, conf)
cg.add(getattr(paren, f"set_{type}")(sens))

View File

@@ -0,0 +1,106 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import output
from esphome.const import CONF_ID, CONF_VALUE
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID, pipsolar_ns
DEPENDENCIES = ["pipsolar"]
PipsolarOutput = pipsolar_ns.class_("PipsolarOutput", output.FloatOutput)
SetOutputAction = pipsolar_ns.class_("SetOutputAction", automation.Action)
CONF_POSSIBLE_VALUES = "possible_values"
# 3.11 PCVV<nn.n><cr>: Setting battery C.V. (constant voltage) charging voltage 48.0V ~ 58.4V for 48V unit
# battery_bulk_voltage;
# battery_recharge_voltage; 12V unit: 11V/11.3V/11.5V/11.8V/12V/12.3V/12.5V/12.8V
# 24V unit: 22V/22.5V/23V/23.5V/24V/24.5V/25V/25.5V
# 48V unit: 44V/45V/46V/47V/48V/49V/50V/51V
# battery_under_voltage; 40.0V ~ 48.0V for 48V unit
# battery_float_voltage; 48.0V ~ 58.4V for 48V unit
# battery_type; 00 for AGM, 01 for Flooded battery
# current_max_ac_charging_current;
# output_source_priority; 00 / 01 / 02
# charger_source_priority; For HS: 00 for utility first, 01 for solar first, 02 for solar and utility, 03 for only solar charging
# For MS/MSX: 00 for utility first, 01 for solar first, 03 for only solar charging
# battery_redischarge_voltage; 12V unit: 00.0V12V/12.3V/12.5V/12.8V/13V/13.3V/13.5V/13.8V/14V/14.3V/14.5
# 24V unit: 00.0V/24V/24.5V/25V/25.5V/26V/26.5V/27V/27.5V/28V/28.5V/29V
# 48V unit: 00.0V48V/49V/50V/51V/52V/53V/54V/55V/56V/57V/58V
CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage"
CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage"
CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage"
CONF_BATTERY_TYPE = "battery_type"
CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current"
CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current"
CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority"
CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority"
CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage"
TYPES = {
CONF_BATTERY_RECHARGE_VOLTAGE: (
[44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0],
"PBCV%02.1f",
),
CONF_BATTERY_UNDER_VOLTAGE: (
[40.0, 40.1, 42, 43, 44, 45, 46, 47, 48.0],
"PSDV%02.1f",
),
CONF_BATTERY_FLOAT_VOLTAGE: ([48.0, 49.0, 50.0, 51.0], "PBFT%02.1f"),
CONF_BATTERY_TYPE: ([0, 1, 2], "PBT%02.0f"),
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: ([2, 10, 20], "MUCHGC0%02.0f"),
CONF_CURRENT_MAX_CHARGING_CURRENT: ([10, 20, 30, 40], "MCHGC0%02.0f"),
CONF_OUTPUT_SOURCE_PRIORITY: ([0, 1, 2], "POP%02.0f"),
CONF_CHARGER_SOURCE_PRIORITY: ([0, 1, 2, 3], "PCP%02.0f"),
CONF_BATTERY_REDISCHARGE_VOLTAGE: (
[0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],
"PBDV%02.1f",
),
}
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{
cv.Optional(type): output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(PipsolarOutput),
cv.Optional(CONF_POSSIBLE_VALUES, default=values): cv.All(
cv.ensure_list(cv.positive_float), cv.Length(min=1)
),
}
)
for type, (values, _) in TYPES.items()
}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, (_, command) in TYPES.items():
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await output.register_output(var, conf)
cg.add(var.set_parent(paren))
cg.add(var.set_set_command(command))
if (CONF_POSSIBLE_VALUES) in conf:
cg.add(var.set_possible_values(conf[CONF_POSSIBLE_VALUES]))
@automation.register_action(
"output.pipsolar.set_level",
SetOutputAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(CONF_ID),
cv.Required(CONF_VALUE): cv.templatable(cv.positive_float),
}
),
)
def output_pipsolar_set_level_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = yield cg.templatable(config[CONF_VALUE], args, float)
cg.add(var.set_level(template_))
yield var

View File

@@ -0,0 +1,22 @@
#include "pipsolar_output.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.output";
void PipsolarOutput::write_state(float state) {
char tmp[10];
sprintf(tmp, this->set_command_.c_str(), state);
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);
this->parent_->switch_command(std::string(tmp));
} else {
ESP_LOGD(TAG, "Will not write: %s as it is not in list of allowed values", tmp);
}
}
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,40 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/output/float_output.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarOutput : public output::FloatOutput {
public:
PipsolarOutput() {}
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
void set_set_command(std::string command) { this->set_command_ = std::move(command); };
void set_possible_values(std::vector<float> possible_values) { this->possible_values_ = std::move(possible_values); }
void set_value(float value) { this->write_state(value); };
protected:
void write_state(float state) override;
std::string set_command_;
Pipsolar *parent_;
std::vector<float> possible_values_;
};
template<typename... Ts> class SetOutputAction : public Action<Ts...> {
public:
SetOutputAction(PipsolarOutput *output) : output_(output) {}
TEMPLATABLE_VALUE(float, level)
void play(Ts... x) override { this->output_->set_value(this->level_.value(x...)); }
protected:
PipsolarOutput *output_;
};
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,922 @@
#include "pipsolar.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar";
void Pipsolar::setup() {
this->state_ = STATE_IDLE;
this->command_start_millis_ = 0;
}
void Pipsolar::empty_uart_buffer_() {
uint8_t byte;
while (this->available()) {
this->read_byte(&byte);
}
}
void Pipsolar::loop() {
// Read message
if (this->state_ == STATE_IDLE) {
this->empty_uart_buffer_();
switch (this->send_next_command_()) {
case 0:
// no command send (empty queue) time to poll
if (millis() - this->last_poll_ > this->update_interval_) {
this->send_next_poll_();
this->last_poll_ = millis();
}
return;
break;
case 1:
// command send
return;
break;
}
}
if (this->state_ == STATE_COMMAND_COMPLETE) {
if (this->check_incoming_length_(4)) {
ESP_LOGD(TAG, "response length for command OK");
if (this->check_incoming_crc_()) {
// crc ok
if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
ESP_LOGD(TAG, "command successful");
} else {
ESP_LOGD(TAG, "command not successful");
}
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
} else {
// crc failed
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
}
} else {
ESP_LOGD(TAG, "response length for command %s not OK: with length %zu",
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
}
}
if (this->state_ == STATE_POLL_DECODED) {
std::string mode;
switch (this->used_polling_commands_[this->last_polling_command_].identifier) {
case POLLING_QPIRI:
if (this->grid_rating_voltage_) {
this->grid_rating_voltage_->publish_state(value_grid_rating_voltage_);
}
if (this->grid_rating_current_) {
this->grid_rating_current_->publish_state(value_grid_rating_current_);
}
if (this->ac_output_rating_voltage_) {
this->ac_output_rating_voltage_->publish_state(value_ac_output_rating_voltage_);
}
if (this->ac_output_rating_frequency_) {
this->ac_output_rating_frequency_->publish_state(value_ac_output_rating_frequency_);
}
if (this->ac_output_rating_current_) {
this->ac_output_rating_current_->publish_state(value_ac_output_rating_current_);
}
if (this->ac_output_rating_apparent_power_) {
this->ac_output_rating_apparent_power_->publish_state(value_ac_output_rating_apparent_power_);
}
if (this->ac_output_rating_active_power_) {
this->ac_output_rating_active_power_->publish_state(value_ac_output_rating_active_power_);
}
if (this->battery_rating_voltage_) {
this->battery_rating_voltage_->publish_state(value_battery_rating_voltage_);
}
if (this->battery_recharge_voltage_) {
this->battery_recharge_voltage_->publish_state(value_battery_recharge_voltage_);
}
if (this->battery_under_voltage_) {
this->battery_under_voltage_->publish_state(value_battery_under_voltage_);
}
if (this->battery_bulk_voltage_) {
this->battery_bulk_voltage_->publish_state(value_battery_bulk_voltage_);
}
if (this->battery_float_voltage_) {
this->battery_float_voltage_->publish_state(value_battery_float_voltage_);
}
if (this->battery_type_) {
this->battery_type_->publish_state(value_battery_type_);
}
if (this->current_max_ac_charging_current_) {
this->current_max_ac_charging_current_->publish_state(value_current_max_ac_charging_current_);
}
if (this->current_max_charging_current_) {
this->current_max_charging_current_->publish_state(value_current_max_charging_current_);
}
if (this->input_voltage_range_) {
this->input_voltage_range_->publish_state(value_input_voltage_range_);
}
// special for input voltage range switch
if (this->input_voltage_range_switch_) {
this->input_voltage_range_switch_->publish_state(value_input_voltage_range_ == 1);
}
if (this->output_source_priority_) {
this->output_source_priority_->publish_state(value_output_source_priority_);
}
// special for output source priority switches
if (this->output_source_priority_utility_switch_) {
this->output_source_priority_utility_switch_->publish_state(value_output_source_priority_ == 0);
}
if (this->output_source_priority_solar_switch_) {
this->output_source_priority_solar_switch_->publish_state(value_output_source_priority_ == 1);
}
if (this->output_source_priority_battery_switch_) {
this->output_source_priority_battery_switch_->publish_state(value_output_source_priority_ == 2);
}
if (this->charger_source_priority_) {
this->charger_source_priority_->publish_state(value_charger_source_priority_);
}
if (this->parallel_max_num_) {
this->parallel_max_num_->publish_state(value_parallel_max_num_);
}
if (this->machine_type_) {
this->machine_type_->publish_state(value_machine_type_);
}
if (this->topology_) {
this->topology_->publish_state(value_topology_);
}
if (this->output_mode_) {
this->output_mode_->publish_state(value_output_mode_);
}
if (this->battery_redischarge_voltage_) {
this->battery_redischarge_voltage_->publish_state(value_battery_redischarge_voltage_);
}
if (this->pv_ok_condition_for_parallel_) {
this->pv_ok_condition_for_parallel_->publish_state(value_pv_ok_condition_for_parallel_);
}
// special for pv ok condition switch
if (this->pv_ok_condition_for_parallel_switch_) {
this->pv_ok_condition_for_parallel_switch_->publish_state(value_pv_ok_condition_for_parallel_ == 1);
}
if (this->pv_power_balance_) {
this->pv_power_balance_->publish_state(value_pv_power_balance_ == 1);
}
// special for power balance switch
if (this->pv_power_balance_switch_) {
this->pv_power_balance_switch_->publish_state(value_pv_power_balance_ == 1);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QPIGS:
if (this->grid_voltage_) {
this->grid_voltage_->publish_state(value_grid_voltage_);
}
if (this->grid_frequency_) {
this->grid_frequency_->publish_state(value_grid_frequency_);
}
if (this->ac_output_voltage_) {
this->ac_output_voltage_->publish_state(value_ac_output_voltage_);
}
if (this->ac_output_frequency_) {
this->ac_output_frequency_->publish_state(value_ac_output_frequency_);
}
if (this->ac_output_apparent_power_) {
this->ac_output_apparent_power_->publish_state(value_ac_output_apparent_power_);
}
if (this->ac_output_active_power_) {
this->ac_output_active_power_->publish_state(value_ac_output_active_power_);
}
if (this->output_load_percent_) {
this->output_load_percent_->publish_state(value_output_load_percent_);
}
if (this->bus_voltage_) {
this->bus_voltage_->publish_state(value_bus_voltage_);
}
if (this->battery_voltage_) {
this->battery_voltage_->publish_state(value_battery_voltage_);
}
if (this->battery_charging_current_) {
this->battery_charging_current_->publish_state(value_battery_charging_current_);
}
if (this->battery_capacity_percent_) {
this->battery_capacity_percent_->publish_state(value_battery_capacity_percent_);
}
if (this->inverter_heat_sink_temperature_) {
this->inverter_heat_sink_temperature_->publish_state(value_inverter_heat_sink_temperature_);
}
if (this->pv_input_current_for_battery_) {
this->pv_input_current_for_battery_->publish_state(value_pv_input_current_for_battery_);
}
if (this->pv_input_voltage_) {
this->pv_input_voltage_->publish_state(value_pv_input_voltage_);
}
if (this->battery_voltage_scc_) {
this->battery_voltage_scc_->publish_state(value_battery_voltage_scc_);
}
if (this->battery_discharge_current_) {
this->battery_discharge_current_->publish_state(value_battery_discharge_current_);
}
if (this->add_sbu_priority_version_) {
this->add_sbu_priority_version_->publish_state(value_add_sbu_priority_version_);
}
if (this->configuration_status_) {
this->configuration_status_->publish_state(value_configuration_status_);
}
if (this->scc_firmware_version_) {
this->scc_firmware_version_->publish_state(value_scc_firmware_version_);
}
if (this->load_status_) {
this->load_status_->publish_state(value_load_status_);
}
if (this->battery_voltage_to_steady_while_charging_) {
this->battery_voltage_to_steady_while_charging_->publish_state(
value_battery_voltage_to_steady_while_charging_);
}
if (this->charging_status_) {
this->charging_status_->publish_state(value_charging_status_);
}
if (this->scc_charging_status_) {
this->scc_charging_status_->publish_state(value_scc_charging_status_);
}
if (this->ac_charging_status_) {
this->ac_charging_status_->publish_state(value_ac_charging_status_);
}
if (this->battery_voltage_offset_for_fans_on_) {
this->battery_voltage_offset_for_fans_on_->publish_state(value_battery_voltage_offset_for_fans_on_ / 10.0f);
} //.1 scale
if (this->eeprom_version_) {
this->eeprom_version_->publish_state(value_eeprom_version_);
}
if (this->pv_charging_power_) {
this->pv_charging_power_->publish_state(value_pv_charging_power_);
}
if (this->charging_to_floating_mode_) {
this->charging_to_floating_mode_->publish_state(value_charging_to_floating_mode_);
}
if (this->switch_on_) {
this->switch_on_->publish_state(value_switch_on_);
}
if (this->dustproof_installed_) {
this->dustproof_installed_->publish_state(value_dustproof_installed_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QMOD:
if (this->device_mode_) {
mode = value_device_mode_;
this->device_mode_->publish_state(mode);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QFLAG:
if (this->silence_buzzer_open_buzzer_) {
this->silence_buzzer_open_buzzer_->publish_state(value_silence_buzzer_open_buzzer_);
}
if (this->overload_bypass_function_) {
this->overload_bypass_function_->publish_state(value_overload_bypass_function_);
}
if (this->lcd_escape_to_default_) {
this->lcd_escape_to_default_->publish_state(value_lcd_escape_to_default_);
}
if (this->overload_restart_function_) {
this->overload_restart_function_->publish_state(value_overload_restart_function_);
}
if (this->over_temperature_restart_function_) {
this->over_temperature_restart_function_->publish_state(value_over_temperature_restart_function_);
}
if (this->backlight_on_) {
this->backlight_on_->publish_state(value_backlight_on_);
}
if (this->alarm_on_when_primary_source_interrupt_) {
this->alarm_on_when_primary_source_interrupt_->publish_state(value_alarm_on_when_primary_source_interrupt_);
}
if (this->fault_code_record_) {
this->fault_code_record_->publish_state(value_fault_code_record_);
}
if (this->power_saving_) {
this->power_saving_->publish_state(value_power_saving_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QPIWS:
if (this->warnings_present_) {
this->warnings_present_->publish_state(value_warnings_present_);
}
if (this->faults_present_) {
this->faults_present_->publish_state(value_faults_present_);
}
if (this->warning_power_loss_) {
this->warning_power_loss_->publish_state(value_warning_power_loss_);
}
if (this->fault_inverter_fault_) {
this->fault_inverter_fault_->publish_state(value_fault_inverter_fault_);
}
if (this->fault_bus_over_) {
this->fault_bus_over_->publish_state(value_fault_bus_over_);
}
if (this->fault_bus_under_) {
this->fault_bus_under_->publish_state(value_fault_bus_under_);
}
if (this->fault_bus_soft_fail_) {
this->fault_bus_soft_fail_->publish_state(value_fault_bus_soft_fail_);
}
if (this->warning_line_fail_) {
this->warning_line_fail_->publish_state(value_warning_line_fail_);
}
if (this->fault_opvshort_) {
this->fault_opvshort_->publish_state(value_fault_opvshort_);
}
if (this->fault_inverter_voltage_too_low_) {
this->fault_inverter_voltage_too_low_->publish_state(value_fault_inverter_voltage_too_low_);
}
if (this->fault_inverter_voltage_too_high_) {
this->fault_inverter_voltage_too_high_->publish_state(value_fault_inverter_voltage_too_high_);
}
if (this->warning_over_temperature_) {
this->warning_over_temperature_->publish_state(value_warning_over_temperature_);
}
if (this->warning_fan_lock_) {
this->warning_fan_lock_->publish_state(value_warning_fan_lock_);
}
if (this->warning_battery_voltage_high_) {
this->warning_battery_voltage_high_->publish_state(value_warning_battery_voltage_high_);
}
if (this->warning_battery_low_alarm_) {
this->warning_battery_low_alarm_->publish_state(value_warning_battery_low_alarm_);
}
if (this->warning_battery_under_shutdown_) {
this->warning_battery_under_shutdown_->publish_state(value_warning_battery_under_shutdown_);
}
if (this->warning_battery_derating_) {
this->warning_battery_derating_->publish_state(value_warning_battery_derating_);
}
if (this->warning_over_load_) {
this->warning_over_load_->publish_state(value_warning_over_load_);
}
if (this->warning_eeprom_failed_) {
this->warning_eeprom_failed_->publish_state(value_warning_eeprom_failed_);
}
if (this->fault_inverter_over_current_) {
this->fault_inverter_over_current_->publish_state(value_fault_inverter_over_current_);
}
if (this->fault_inverter_soft_failed_) {
this->fault_inverter_soft_failed_->publish_state(value_fault_inverter_soft_failed_);
}
if (this->fault_self_test_failed_) {
this->fault_self_test_failed_->publish_state(value_fault_self_test_failed_);
}
if (this->fault_op_dc_voltage_over_) {
this->fault_op_dc_voltage_over_->publish_state(value_fault_op_dc_voltage_over_);
}
if (this->fault_battery_open_) {
this->fault_battery_open_->publish_state(value_fault_battery_open_);
}
if (this->fault_current_sensor_failed_) {
this->fault_current_sensor_failed_->publish_state(value_fault_current_sensor_failed_);
}
if (this->fault_battery_short_) {
this->fault_battery_short_->publish_state(value_fault_battery_short_);
}
if (this->warning_power_limit_) {
this->warning_power_limit_->publish_state(value_warning_power_limit_);
}
if (this->warning_pv_voltage_high_) {
this->warning_pv_voltage_high_->publish_state(value_warning_pv_voltage_high_);
}
if (this->fault_mppt_overload_) {
this->fault_mppt_overload_->publish_state(value_fault_mppt_overload_);
}
if (this->warning_mppt_overload_) {
this->warning_mppt_overload_->publish_state(value_warning_mppt_overload_);
}
if (this->warning_battery_too_low_to_charge_) {
this->warning_battery_too_low_to_charge_->publish_state(value_warning_battery_too_low_to_charge_);
}
if (this->fault_dc_dc_over_current_) {
this->fault_dc_dc_over_current_->publish_state(value_fault_dc_dc_over_current_);
}
if (this->fault_code_) {
this->fault_code_->publish_state(value_fault_code_);
}
if (this->warnung_low_pv_energy_) {
this->warnung_low_pv_energy_->publish_state(value_warnung_low_pv_energy_);
}
if (this->warning_high_ac_input_during_bus_soft_start_) {
this->warning_high_ac_input_during_bus_soft_start_->publish_state(
value_warning_high_ac_input_during_bus_soft_start_);
}
if (this->warning_battery_equalization_) {
this->warning_battery_equalization_->publish_state(value_warning_battery_equalization_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QT:
this->state_ = STATE_IDLE;
break;
case POLLING_QMN:
this->state_ = STATE_IDLE;
break;
}
}
if (this->state_ == STATE_POLL_CHECKED) {
bool enabled = true;
std::string fc;
char tmp[PIPSOLAR_READ_BUFFER_LENGTH];
sprintf(tmp, "%s", this->read_buffer_);
switch (this->used_polling_commands_[this->last_polling_command_].identifier) {
case POLLING_QPIRI:
ESP_LOGD(TAG, "Decode QPIRI");
sscanf(tmp, "(%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d %d %d %d %d %f %d %d", // NOLINT
&value_grid_rating_voltage_, &value_grid_rating_current_, &value_ac_output_rating_voltage_, // NOLINT
&value_ac_output_rating_frequency_, &value_ac_output_rating_current_, // NOLINT
&value_ac_output_rating_apparent_power_, &value_ac_output_rating_active_power_, // NOLINT
&value_battery_rating_voltage_, &value_battery_recharge_voltage_, // NOLINT
&value_battery_under_voltage_, &value_battery_bulk_voltage_, &value_battery_float_voltage_, // NOLINT
&value_battery_type_, &value_current_max_ac_charging_current_, // NOLINT
&value_current_max_charging_current_, &value_input_voltage_range_, // NOLINT
&value_output_source_priority_, &value_charger_source_priority_, &value_parallel_max_num_, // NOLINT
&value_machine_type_, &value_topology_, &value_output_mode_, // NOLINT
&value_battery_redischarge_voltage_, &value_pv_ok_condition_for_parallel_, // NOLINT
&value_pv_power_balance_); // NOLINT
if (this->last_qpiri_) {
this->last_qpiri_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QPIGS:
ESP_LOGD(TAG, "Decode QPIGS");
sscanf( // NOLINT
tmp, // NOLINT
"(%f %f %f %f %d %d %d %d %f %d %d %d %d %f %f %d %1d%1d%1d%1d%1d%1d%1d%1d %d %d %d %1d%1d%1d", // NOLINT
&value_grid_voltage_, &value_grid_frequency_, &value_ac_output_voltage_, // NOLINT
&value_ac_output_frequency_, // NOLINT
&value_ac_output_apparent_power_, &value_ac_output_active_power_, &value_output_load_percent_, // NOLINT
&value_bus_voltage_, &value_battery_voltage_, &value_battery_charging_current_, // NOLINT
&value_battery_capacity_percent_, &value_inverter_heat_sink_temperature_, // NOLINT
&value_pv_input_current_for_battery_, &value_pv_input_voltage_, &value_battery_voltage_scc_, // NOLINT
&value_battery_discharge_current_, &value_add_sbu_priority_version_, // NOLINT
&value_configuration_status_, &value_scc_firmware_version_, &value_load_status_, // NOLINT
&value_battery_voltage_to_steady_while_charging_, &value_charging_status_, // NOLINT
&value_scc_charging_status_, &value_ac_charging_status_, // NOLINT
&value_battery_voltage_offset_for_fans_on_, &value_eeprom_version_, &value_pv_charging_power_, // NOLINT
&value_charging_to_floating_mode_, &value_switch_on_, // NOLINT
&value_dustproof_installed_); // NOLINT
if (this->last_qpigs_) {
this->last_qpigs_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QMOD:
ESP_LOGD(TAG, "Decode QMOD");
this->value_device_mode_ = char(this->read_buffer_[1]);
if (this->last_qmod_) {
this->last_qmod_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QFLAG:
ESP_LOGD(TAG, "Decode QFLAG");
// result like:"(EbkuvxzDajy"
// get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value
for (int i = 1; i < strlen(tmp); i++) {
switch (tmp[i]) {
case 'E':
enabled = true;
break;
case 'D':
enabled = false;
break;
case 'a':
this->value_silence_buzzer_open_buzzer_ = enabled;
break;
case 'b':
this->value_overload_bypass_function_ = enabled;
break;
case 'k':
this->value_lcd_escape_to_default_ = enabled;
break;
case 'u':
this->value_overload_restart_function_ = enabled;
break;
case 'v':
this->value_over_temperature_restart_function_ = enabled;
break;
case 'x':
this->value_backlight_on_ = enabled;
break;
case 'y':
this->value_alarm_on_when_primary_source_interrupt_ = enabled;
break;
case 'z':
this->value_fault_code_record_ = enabled;
break;
case 'j':
this->value_power_saving_ = enabled;
break;
}
}
if (this->last_qflag_) {
this->last_qflag_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QPIWS:
ESP_LOGD(TAG, "Decode QPIWS");
// '(00000000000000000000000000000000'
// iterate over all available flag (as not all models have all flags, but at least in the same order)
this->value_warnings_present_ = false;
this->value_faults_present_ = true;
for (int i = 1; i < strlen(tmp); i++) {
enabled = tmp[i] == '1';
switch (i) {
case 1:
this->value_warning_power_loss_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 2:
this->value_fault_inverter_fault_ = enabled;
this->value_faults_present_ += enabled;
break;
case 3:
this->value_fault_bus_over_ = enabled;
this->value_faults_present_ += enabled;
break;
case 4:
this->value_fault_bus_under_ = enabled;
this->value_faults_present_ += enabled;
break;
case 5:
this->value_fault_bus_soft_fail_ = enabled;
this->value_faults_present_ += enabled;
break;
case 6:
this->value_warning_line_fail_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 7:
this->value_fault_opvshort_ = enabled;
this->value_faults_present_ += enabled;
break;
case 8:
this->value_fault_inverter_voltage_too_low_ = enabled;
this->value_faults_present_ += enabled;
break;
case 9:
this->value_fault_inverter_voltage_too_high_ = enabled;
this->value_faults_present_ += enabled;
break;
case 10:
this->value_warning_over_temperature_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 11:
this->value_warning_fan_lock_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 12:
this->value_warning_battery_voltage_high_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 13:
this->value_warning_battery_low_alarm_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 15:
this->value_warning_battery_under_shutdown_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 16:
this->value_warning_battery_derating_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 17:
this->value_warning_over_load_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 18:
this->value_warning_eeprom_failed_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 19:
this->value_fault_inverter_over_current_ = enabled;
this->value_faults_present_ += enabled;
break;
case 20:
this->value_fault_inverter_soft_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 21:
this->value_fault_self_test_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 22:
this->value_fault_op_dc_voltage_over_ = enabled;
this->value_faults_present_ += enabled;
break;
case 23:
this->value_fault_battery_open_ = enabled;
this->value_faults_present_ += enabled;
break;
case 24:
this->value_fault_current_sensor_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 25:
this->value_fault_battery_short_ = enabled;
this->value_faults_present_ += enabled;
break;
case 26:
this->value_warning_power_limit_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 27:
this->value_warning_pv_voltage_high_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 28:
this->value_fault_mppt_overload_ = enabled;
this->value_faults_present_ += enabled;
break;
case 29:
this->value_warning_mppt_overload_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 30:
this->value_warning_battery_too_low_to_charge_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 31:
this->value_fault_dc_dc_over_current_ = enabled;
this->value_faults_present_ += enabled;
break;
case 32:
fc = tmp[i];
fc += tmp[i + 1];
this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10);
break;
case 34:
this->value_warnung_low_pv_energy_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 35:
this->value_warning_high_ac_input_during_bus_soft_start_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 36:
this->value_warning_battery_equalization_ = enabled;
this->value_warnings_present_ += enabled;
break;
}
}
if (this->last_qpiws_) {
this->last_qpiws_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QT:
ESP_LOGD(TAG, "Decode QT");
if (this->last_qt_) {
this->last_qt_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QMN:
ESP_LOGD(TAG, "Decode QMN");
if (this->last_qmn_) {
this->last_qmn_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
default:
this->state_ = STATE_IDLE;
break;
}
return;
}
if (this->state_ == STATE_POLL_COMPLETE) {
if (this->check_incoming_crc_()) {
if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
this->read_buffer_[3] == 'K') {
this->state_ = STATE_IDLE;
return;
}
// crc ok
this->state_ = STATE_POLL_CHECKED;
return;
} else {
this->state_ = STATE_IDLE;
}
}
if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) {
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == PIPSOLAR_READ_BUFFER_LENGTH) {
this->read_pos_ = 0;
this->empty_uart_buffer_();
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
// end of answer
if (byte == 0x0D) {
this->read_buffer_[this->read_pos_] = 0;
this->empty_uart_buffer_();
if (this->state_ == STATE_POLL) {
this->state_ = STATE_POLL_COMPLETE;
}
if (this->state_ == STATE_COMMAND) {
this->state_ = STATE_COMMAND_COMPLETE;
}
}
} // available
}
if (this->state_ == STATE_COMMAND) {
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
// command timeout
const char *command = this->command_queue_[this->command_queue_position_].c_str();
this->command_start_millis_ = millis();
ESP_LOGD(TAG, "timeout command from queue: %s", command);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
return;
} else {
}
}
if (this->state_ == STATE_POLL) {
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
// command timeout
ESP_LOGD(TAG, "timeout command to poll: %s", this->used_polling_commands_[this->last_polling_command_].command);
this->state_ = STATE_IDLE;
} else {
}
}
}
uint8_t Pipsolar::check_incoming_length_(uint8_t length) {
if (this->read_pos_ - 3 == length) {
return 1;
}
return 0;
}
uint8_t Pipsolar::check_incoming_crc_() {
uint16_t crc16;
crc16 = calc_crc_(read_buffer_, read_pos_ - 3);
ESP_LOGD(TAG, "checking crc on incoming message");
if (((uint8_t)((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
((uint8_t)((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
ESP_LOGD(TAG, "CRC OK");
read_buffer_[read_pos_ - 1] = 0;
read_buffer_[read_pos_ - 2] = 0;
read_buffer_[read_pos_ - 3] = 0;
return 1;
}
ESP_LOGD(TAG, "CRC NOK expected: %X %X but got: %X %X", ((uint8_t)((crc16) >> 8)), ((uint8_t)((crc16) &0xff)),
read_buffer_[read_pos_ - 3], read_buffer_[read_pos_ - 2]);
return 0;
}
// send next command used
uint8_t Pipsolar::send_next_command_() {
uint16_t crc16;
if (this->command_queue_[this->command_queue_position_].length() != 0) {
const char *command = this->command_queue_[this->command_queue_position_].c_str();
uint8_t byte_command[16];
uint8_t length = this->command_queue_[this->command_queue_position_].length();
for (uint8_t i = 0; i < length; i++) {
byte_command[i] = (uint8_t) this->command_queue_[this->command_queue_position_].at(i);
}
this->state_ = STATE_COMMAND;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = calc_crc_(byte_command, length);
this->write_str(command);
// checksum
this->write(((uint8_t)((crc16) >> 8))); // highbyte
this->write(((uint8_t)((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length);
return 1;
}
return 0;
}
void Pipsolar::send_next_poll_() {
uint16_t crc16;
this->last_polling_command_ = (this->last_polling_command_ + 1) % 15;
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
this->last_polling_command_ = 0;
}
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
// no command specified
return;
}
this->state_ = STATE_POLL;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = calc_crc_(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
this->write_array(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
// checksum
this->write(((uint8_t)((crc16) >> 8))); // highbyte
this->write(((uint8_t)((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
}
void Pipsolar::queue_command_(const char *command, byte length) {
uint8_t next_position = command_queue_position_;
for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) {
uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH;
if (command_queue_[testposition].length() == 0) {
command_queue_[testposition] = command;
ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command,
command_queue_[testposition].length(), testposition);
return;
}
}
ESP_LOGD(TAG, "Command queue full dropping command: %s", command);
}
void Pipsolar::switch_command(const std::string &command) {
ESP_LOGD(TAG, "got command: %s", command.c_str());
queue_command_(command.c_str(), command.length());
}
void Pipsolar::dump_config() {
ESP_LOGCONFIG(TAG, "Pipsolar:");
ESP_LOGCONFIG(TAG, "used commands:");
for (auto &used_polling_command : this->used_polling_commands_) {
if (used_polling_command.length != 0) {
ESP_LOGCONFIG(TAG, "%s", used_polling_command.command);
}
}
}
void Pipsolar::update() {}
void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) {
for (auto &used_polling_command : this->used_polling_commands_) {
if (used_polling_command.length == strlen(command)) {
uint8_t len = strlen(command);
if (memcmp(used_polling_command.command, command, len) == 0) {
return;
}
}
if (used_polling_command.length == 0) {
size_t length = strlen(command) + 1;
const char *beg = command;
const char *end = command + length;
used_polling_command.command = new uint8_t[length];
size_t i = 0;
for (; beg != end; ++beg, ++i) {
used_polling_command.command[i] = (uint8_t)(*beg);
}
used_polling_command.errors = 0;
used_polling_command.identifier = polling_command;
used_polling_command.length = length - 1;
return;
}
}
}
uint16_t Pipsolar::calc_crc_(uint8_t *msg, int n) {
// Initial value. xmodem uses 0xFFFF but this example
// requires an initial value of zero.
uint16_t x = 0;
while (n--) {
x = crc_xmodem_update_(x, (uint16_t) *msg++);
}
return (x);
}
// See bottom of this page: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
// Polynomial: x^16 + x^12 + x^5 + 1 (0x1021)
uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) {
int i;
crc = crc ^ ((uint16_t) data << 8);
for (i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021)
else
crc <<= 1;
}
return crc;
}
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,223 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
enum ENUMPollingCommand {
POLLING_QPIRI = 0,
POLLING_QPIGS = 1,
POLLING_QMOD = 2,
POLLING_QFLAG = 3,
POLLING_QPIWS = 4,
POLLING_QT = 5,
POLLING_QMN = 6,
};
struct PollingCommand {
uint8_t *command;
uint8_t length = 0;
uint8_t errors;
ENUMPollingCommand identifier;
};
#define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \
protected: \
value_type value_##name##_; \
PIPSOLAR_ENTITY_(type, name, polling_command)
#define PIPSOLAR_ENTITY_(type, name, polling_command) \
protected: \
type *name##_{}; /* NOLINT */ \
\
public: \
void set_##name(type *name) { /* NOLINT */ \
this->name##_ = name; \
this->add_polling_command_(#polling_command, POLLING_##polling_command); \
}
#define PIPSOLAR_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(sensor::Sensor, name, polling_command, value_type)
#define PIPSOLAR_SWITCH(name, polling_command) PIPSOLAR_ENTITY_(switch_::Switch, name, polling_command)
#define PIPSOLAR_BINARY_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(binary_sensor::BinarySensor, name, polling_command, value_type)
#define PIPSOLAR_VALUED_TEXT_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(text_sensor::TextSensor, name, polling_command, value_type)
#define PIPSOLAR_TEXT_SENSOR(name, polling_command) PIPSOLAR_ENTITY_(text_sensor::TextSensor, name, polling_command)
class Pipsolar : public uart::UARTDevice, public PollingComponent {
// QPIGS values
PIPSOLAR_SENSOR(grid_voltage, QPIGS, float)
PIPSOLAR_SENSOR(grid_frequency, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_voltage, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_frequency, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_apparent_power, QPIGS, int)
PIPSOLAR_SENSOR(ac_output_active_power, QPIGS, int)
PIPSOLAR_SENSOR(output_load_percent, QPIGS, int)
PIPSOLAR_SENSOR(bus_voltage, QPIGS, int)
PIPSOLAR_SENSOR(battery_voltage, QPIGS, float)
PIPSOLAR_SENSOR(battery_charging_current, QPIGS, int)
PIPSOLAR_SENSOR(battery_capacity_percent, QPIGS, int)
PIPSOLAR_SENSOR(inverter_heat_sink_temperature, QPIGS, int)
PIPSOLAR_SENSOR(pv_input_current_for_battery, QPIGS, int)
PIPSOLAR_SENSOR(pv_input_voltage, QPIGS, float)
PIPSOLAR_SENSOR(battery_voltage_scc, QPIGS, float)
PIPSOLAR_SENSOR(battery_discharge_current, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(add_sbu_priority_version, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(configuration_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(scc_firmware_version, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(load_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(battery_voltage_to_steady_while_charging, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(charging_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(scc_charging_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(ac_charging_status, QPIGS, int)
PIPSOLAR_SENSOR(battery_voltage_offset_for_fans_on, QPIGS, int) //.1 scale
PIPSOLAR_SENSOR(eeprom_version, QPIGS, int)
PIPSOLAR_SENSOR(pv_charging_power, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(charging_to_floating_mode, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(switch_on, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(dustproof_installed, QPIGS, int)
// QPIRI values
PIPSOLAR_SENSOR(grid_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(grid_rating_current, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_frequency, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_current, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_apparent_power, QPIRI, int)
PIPSOLAR_SENSOR(ac_output_rating_active_power, QPIRI, int)
PIPSOLAR_SENSOR(battery_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_recharge_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_under_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_bulk_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_float_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_type, QPIRI, int)
PIPSOLAR_SENSOR(current_max_ac_charging_current, QPIRI, int)
PIPSOLAR_SENSOR(current_max_charging_current, QPIRI, int)
PIPSOLAR_SENSOR(input_voltage_range, QPIRI, int)
PIPSOLAR_SENSOR(output_source_priority, QPIRI, int)
PIPSOLAR_SENSOR(charger_source_priority, QPIRI, int)
PIPSOLAR_SENSOR(parallel_max_num, QPIRI, int)
PIPSOLAR_SENSOR(machine_type, QPIRI, int)
PIPSOLAR_SENSOR(topology, QPIRI, int)
PIPSOLAR_SENSOR(output_mode, QPIRI, int)
PIPSOLAR_SENSOR(battery_redischarge_voltage, QPIRI, float)
PIPSOLAR_SENSOR(pv_ok_condition_for_parallel, QPIRI, int)
PIPSOLAR_SENSOR(pv_power_balance, QPIRI, int)
// QMOD values
PIPSOLAR_VALUED_TEXT_SENSOR(device_mode, QMOD, char)
// QFLAG values
PIPSOLAR_BINARY_SENSOR(silence_buzzer_open_buzzer, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(overload_bypass_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(lcd_escape_to_default, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(overload_restart_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(over_temperature_restart_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(backlight_on, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(alarm_on_when_primary_source_interrupt, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(fault_code_record, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(power_saving, QFLAG, int)
// QPIWS values
PIPSOLAR_BINARY_SENSOR(warnings_present, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(faults_present, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_power_loss, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_fault, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_over, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_under, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_soft_fail, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_line_fail, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_opvshort, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_low, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_over_temperature, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_fan_lock, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_voltage_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_low_alarm, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_under_shutdown, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_derating, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_over_load, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_eeprom_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_over_current, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_soft_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_self_test_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_op_dc_voltage_over, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_battery_open, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_current_sensor_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_battery_short, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_power_limit, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_pv_voltage_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_mppt_overload, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_mppt_overload, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_too_low_to_charge, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_dc_dc_over_current, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_code, QPIWS, int)
PIPSOLAR_BINARY_SENSOR(warnung_low_pv_energy, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_high_ac_input_during_bus_soft_start, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_equalization, QPIWS, bool)
PIPSOLAR_TEXT_SENSOR(last_qpigs, QPIGS)
PIPSOLAR_TEXT_SENSOR(last_qpiri, QPIRI)
PIPSOLAR_TEXT_SENSOR(last_qmod, QMOD)
PIPSOLAR_TEXT_SENSOR(last_qflag, QFLAG)
PIPSOLAR_TEXT_SENSOR(last_qpiws, QPIWS)
PIPSOLAR_TEXT_SENSOR(last_qt, QT)
PIPSOLAR_TEXT_SENSOR(last_qmn, QMN)
PIPSOLAR_SWITCH(output_source_priority_utility_switch, QPIRI)
PIPSOLAR_SWITCH(output_source_priority_solar_switch, QPIRI)
PIPSOLAR_SWITCH(output_source_priority_battery_switch, QPIRI)
PIPSOLAR_SWITCH(input_voltage_range_switch, QPIRI)
PIPSOLAR_SWITCH(pv_ok_condition_for_parallel_switch, QPIRI)
PIPSOLAR_SWITCH(pv_power_balance_switch, QPIRI)
void switch_command(const std::string &command);
void setup() override;
void loop() override;
void dump_config() override;
void update() override;
protected:
static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length
static const size_t COMMAND_QUEUE_LENGTH = 10;
static const size_t COMMAND_TIMEOUT = 5000;
uint32_t last_poll_ = 0;
void add_polling_command_(const char *command, ENUMPollingCommand polling_command);
void empty_uart_buffer_();
uint8_t check_incoming_crc_();
uint8_t check_incoming_length_(uint8_t length);
uint16_t calc_crc_(uint8_t *msg, int n);
uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data);
uint8_t send_next_command_();
void send_next_poll_();
void queue_command_(const char *command, byte length);
std::string command_queue_[COMMAND_QUEUE_LENGTH];
uint8_t command_queue_position_ = 0;
uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH];
size_t read_pos_{0};
uint32_t command_start_millis_ = 0;
uint8_t state_;
enum State {
STATE_IDLE = 0,
STATE_POLL = 1,
STATE_COMMAND = 2,
STATE_POLL_COMPLETE = 3,
STATE_COMMAND_COMPLETE = 4,
STATE_POLL_CHECKED = 5,
STATE_POLL_DECODED = 6,
};
uint8_t last_polling_command_ = 0;
PollingCommand used_polling_commands_[15];
};
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,220 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_EMPTY,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_PERCENT,
UNIT_VOLT,
UNIT_EMPTY,
UNIT_VOLT_AMPS,
UNIT_WATT,
CONF_BUS_VOLTAGE,
CONF_BATTERY_VOLTAGE,
)
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID
DEPENDENCIES = ["uart"]
# QPIRI sensors
CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage"
CONF_GRID_RATING_CURRENT = "grid_rating_current"
CONF_AC_OUTPUT_RATING_VOLTAGE = "ac_output_rating_voltage"
CONF_AC_OUTPUT_RATING_FREQUENCY = "ac_output_rating_frequency"
CONF_AC_OUTPUT_RATING_CURRENT = "ac_output_rating_current"
CONF_AC_OUTPUT_RATING_APPARENT_POWER = "ac_output_rating_apparent_power"
CONF_AC_OUTPUT_RATING_ACTIVE_POWER = "ac_output_rating_active_power"
CONF_BATTERY_RATING_VOLTAGE = "battery_rating_voltage"
CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage"
CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage"
CONF_BATTERY_BULK_VOLTAGE = "battery_bulk_voltage"
CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage"
CONF_BATTERY_TYPE = "battery_type"
CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current"
CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current"
CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range"
CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority"
CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority"
CONF_PARALLEL_MAX_NUM = "parallel_max_num"
CONF_MACHINE_TYPE = "machine_type"
CONF_TOPOLOGY = "topology"
CONF_OUTPUT_MODE = "output_mode"
CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage"
CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel"
CONF_PV_POWER_BALANCE = "pv_power_balance"
CONF_GRID_VOLTAGE = "grid_voltage"
CONF_GRID_FREQUENCY = "grid_frequency"
CONF_AC_OUTPUT_VOLTAGE = "ac_output_voltage"
CONF_AC_OUTPUT_FREQUENCY = "ac_output_frequency"
CONF_AC_OUTPUT_APPARENT_POWER = "ac_output_apparent_power"
CONF_AC_OUTPUT_ACTIVE_POWER = "ac_output_active_power"
CONF_OUTPUT_LOAD_PERCENT = "output_load_percent"
CONF_BATTERY_CHARGING_CURRENT = "battery_charging_current"
CONF_BATTERY_CAPACITY_PERCENT = "battery_capacity_percent"
CONF_INVERTER_HEAT_SINK_TEMPERATURE = "inverter_heat_sink_temperature"
CONF_PV_INPUT_CURRENT_FOR_BATTERY = "pv_input_current_for_battery"
CONF_PV_INPUT_VOLTAGE = "pv_input_voltage"
CONF_BATTERY_VOLTAGE_SCC = "battery_voltage_scc"
CONF_BATTERY_DISCHARGE_CURRENT = "battery_discharge_current"
CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version"
CONF_CONFIGURATION_STATUS = "configuration_status"
CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version"
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON = "battery_voltage_offset_for_fans_on"
CONF_EEPROM_VERSION = "eeprom_version"
CONF_PV_CHARGING_POWER = "pv_charging_power"
TYPES = {
CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_GRID_RATING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_TYPE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PARALLEL_MAX_NUM: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_MACHINE_TYPE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY),
CONF_OUTPUT_MODE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_POWER_BALANCE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_GRID_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_GRID_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_BUS_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
),
CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_EEPROM_VERSION: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_CHARGING_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
}
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): schema for type, schema in TYPES.items()}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, _ in TYPES.items():
if type in config:
conf = config[type]
sens = await sensor.new_sensor(conf)
cg.add(getattr(paren, f"set_{type}")(sens))

View File

@@ -0,0 +1,60 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_ICON,
ICON_POWER,
)
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
DEPENDENCIES = ["uart"]
CONF_OUTPUT_SOURCE_PRIORITY_UTILITY = "output_source_priority_utility"
CONF_OUTPUT_SOURCE_PRIORITY_SOLAR = "output_source_priority_solar"
CONF_OUTPUT_SOURCE_PRIORITY_BATTERY = "output_source_priority_battery"
CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range"
CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel"
CONF_PV_POWER_BALANCE = "pv_power_balance"
TYPES = {
CONF_OUTPUT_SOURCE_PRIORITY_UTILITY: ("POP00", None),
CONF_OUTPUT_SOURCE_PRIORITY_SOLAR: ("POP01", None),
CONF_OUTPUT_SOURCE_PRIORITY_BATTERY: ("POP02", None),
CONF_INPUT_VOLTAGE_RANGE: ("PGR01", "PGR00"),
CONF_PV_OK_CONDITION_FOR_PARALLEL: ("PPVOKC1", "PPVOKC0"),
CONF_PV_POWER_BALANCE: ("PSPB1", "PSPB0"),
}
PipsolarSwitch = pipsolar_ns.class_("PipsolarSwitch", switch.Switch, cg.Component)
PIPSWITCH_SCHEMA = switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PipsolarSwitch),
cv.Optional(CONF_INVERTED): cv.invalid(
"Pipsolar switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): PIPSWITCH_SCHEMA for type in TYPES}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, (on, off) in TYPES.items():
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await switch.register_switch(var, conf)
cg.add(getattr(paren, f"set_{type}_switch")(var))
cg.add(var.set_parent(paren))
cg.add(var.set_on_command(on))
if off is not None:
cg.add(var.set_off_command(off))

View File

@@ -0,0 +1,24 @@
#include "pipsolar_switch.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.switch";
void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); }
void PipsolarSwitch::write_state(bool state) {
if (state) {
if (this->on_command_.length() > 0) {
this->parent_->switch_command(this->on_command_);
}
} else {
if (this->off_command_.length() > 0) {
this->parent_->switch_command(this->off_command_);
}
}
}
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,25 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarSwitch : public switch_::Switch, public Component {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void set_on_command(std::string command) { this->on_command_ = std::move(command); };
void set_off_command(std::string command) { this->off_command_ = std::move(command); };
void dump_config() override;
protected:
void write_state(bool state) override;
std::string on_command_;
std::string off_command_;
Pipsolar *parent_;
};
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,52 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
DEPENDENCIES = ["uart"]
CONF_DEVICE_MODE = "device_mode"
CONF_LAST_QPIGS = "last_qpigs"
CONF_LAST_QPIRI = "last_qpiri"
CONF_LAST_QMOD = "last_qmod"
CONF_LAST_QFLAG = "last_qflag"
CONF_LAST_QPIWS = "last_qpiws"
CONF_LAST_QT = "last_qt"
CONF_LAST_QMN = "last_qmn"
PipsolarTextSensor = pipsolar_ns.class_(
"PipsolarTextSensor", text_sensor.TextSensor, cg.Component
)
TYPES = [
CONF_DEVICE_MODE,
CONF_LAST_QPIGS,
CONF_LAST_QPIRI,
CONF_LAST_QMOD,
CONF_LAST_QFLAG,
CONF_LAST_QPIWS,
CONF_LAST_QT,
CONF_LAST_QMN,
]
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{
cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(PipsolarTextSensor)}
)
for type in TYPES
}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type in TYPES:
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(var, conf)
await cg.register_component(var, conf)
cg.add(getattr(paren, f"set_{type}")(var))

View File

@@ -0,0 +1,13 @@
#include "pipsolar_textsensor.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.text_sensor";
void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); }
} // namespace pipsolar
} // namespace esphome

View File

@@ -0,0 +1,20 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarTextSensor : public Component, public text_sensor::TextSensor {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void dump_config() override;
protected:
Pipsolar *parent_;
};
} // namespace pipsolar
} // namespace esphome

View File

@@ -40,6 +40,7 @@ from esphome.const import (
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_MONETARY,
@@ -62,6 +63,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_MONETARY,
@@ -79,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass")
STATE_CLASSES = {
"": StateClasses.STATE_CLASS_NONE,
"measurement": StateClasses.STATE_CLASS_MEASUREMENT,
"total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING,
}
validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_")

View File

@@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) {
switch (state_class) {
case STATE_CLASS_MEASUREMENT:
return "measurement";
case STATE_CLASS_TOTAL_INCREASING:
return "total_increasing";
case STATE_CLASS_NONE:
default:
return "";
@@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state
void Sensor::set_state_class(const std::string &state_class) {
if (str_equals_case_insensitive(state_class, "measurement")) {
this->state_class = STATE_CLASS_MEASUREMENT;
} else if (str_equals_case_insensitive(state_class, "total_increasing")) {
this->state_class = STATE_CLASS_TOTAL_INCREASING;
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
}

View File

@@ -37,6 +37,7 @@ namespace sensor {
enum StateClass : uint8_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
STATE_CLASS_TOTAL_INCREASING = 2,
};
const char *state_class_to_string(StateClass state_class);

View File

@@ -55,7 +55,7 @@ async def to_code(config):
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_LAMBDA], [], return_type=cg.optional.template(str)
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
)
cg.add(var.set_template(template_))

View File

@@ -57,29 +57,34 @@ void ThermostatClimate::refresh() {
}
bool ThermostatClimate::climate_action_change_delayed() {
bool state_mismatch = this->action != this->compute_action_(true);
switch (this->compute_action_(true)) {
case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE:
return !this->idle_action_ready_();
return state_mismatch && (!this->idle_action_ready_());
case climate::CLIMATE_ACTION_COOLING:
return !this->cooling_action_ready_();
return state_mismatch && (!this->cooling_action_ready_());
case climate::CLIMATE_ACTION_HEATING:
return !this->heating_action_ready_();
return state_mismatch && (!this->heating_action_ready_());
case climate::CLIMATE_ACTION_FAN:
return !this->fanning_action_ready_();
return state_mismatch && (!this->fanning_action_ready_());
case climate::CLIMATE_ACTION_DRYING:
return !this->drying_action_ready_();
return state_mismatch && (!this->drying_action_ready_());
default:
break;
}
return false;
}
bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); }
bool ThermostatClimate::fan_mode_change_delayed() {
bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_;
return state_mismatch && (!this->fan_mode_ready_());
}
climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); }
climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; }
climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; }
bool ThermostatClimate::hysteresis_valid() {
if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) &&
@@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) {
// already in target mode
return;
this->desired_fan_mode_ = fan_mode; // needed for timer callback
this->fan_mode = fan_mode;
if (this->fan_mode_ready_()) {
Trigger<> *trig = this->fan_mode_auto_trigger_;
@@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) {
this->start_timer_(thermostat::TIMER_FAN_MODE);
assert(trig != nullptr);
trig->trigger();
this->fan_mode = fan_mode;
this->prev_fan_mode_ = fan_mode;
this->prev_fan_mode_trigger_ = trig;
}
@@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() {
void ThermostatClimate::fan_mode_timer_callback_() {
ESP_LOGVV(TAG, "fan_mode timer expired");
this->timer_[thermostat::TIMER_FAN_MODE].active = false;
this->switch_to_fan_mode_(this->desired_fan_mode_);
this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON));
if (this->supports_fan_only_action_uses_fan_mode_timer_)
this->switch_to_action_(this->compute_action_());
}

View File

@@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component {
bool fan_mode_change_delayed();
/// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!)
climate::ClimateAction delayed_climate_action();
/// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!)
climate::ClimateFanMode delayed_fan_mode();
/// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!)
climate::ClimateFanMode locked_fan_mode();
/// Set point and hysteresis validation
bool hysteresis_valid(); // returns true if valid
void validate_target_temperature();
@@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component {
Trigger<> *prev_mode_trigger_{nullptr};
Trigger<> *prev_swing_mode_trigger_{nullptr};
/// Desired fan_mode -- used to store desired mode for callback when switching is delayed
climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON};
/// Store previously-known states
///
/// These are used to determine when a trigger/action needs to be called

View File

@@ -20,7 +20,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_(
CONFIG_SCHEMA = (
sensor.sensor_schema(
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset_type=LAST_RESET_TYPE_AUTO,

View File

@@ -54,14 +54,14 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
if (call.get_mode().has_value()) {
const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF;
ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state));
this->parent_->set_datapoint_value(*this->switch_id_, switch_state);
this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state);
}
if (call.get_target_temperature().has_value()) {
const float target_temperature = *call.get_target_temperature();
ESP_LOGV(TAG, "Setting target temperature: %.1f", target_temperature);
this->parent_->set_datapoint_value(*this->target_temperature_id_,
(int) (target_temperature / this->target_temperature_multiplier_));
this->parent_->set_integer_datapoint_value(*this->target_temperature_id_,
(int) (target_temperature / this->target_temperature_multiplier_));
}
}

View File

@@ -67,20 +67,20 @@ void TuyaFan::dump_config() {
void TuyaFan::write_state() {
if (this->switch_id_.has_value()) {
ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state));
this->parent_->set_datapoint_value(*this->switch_id_, this->fan_->state);
this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state);
}
if (this->oscillation_id_.has_value()) {
ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating));
this->parent_->set_datapoint_value(*this->oscillation_id_, this->fan_->oscillating);
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating);
}
if (this->direction_id_.has_value()) {
bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE;
ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable));
this->parent_->set_datapoint_value(*this->direction_id_, enable);
this->parent_->set_boolean_datapoint_value(*this->direction_id_, enable);
}
if (this->speed_id_.has_value()) {
ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed);
this->parent_->set_datapoint_value(*this->speed_id_, this->fan_->speed - 1);
this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1);
}
}

View File

@@ -31,7 +31,7 @@ void TuyaLight::setup() {
});
}
if (min_value_datapoint_id_.has_value()) {
parent_->set_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
}
}
@@ -66,9 +66,9 @@ void TuyaLight::write_state(light::LightState *state) {
if (brightness == 0.0f) {
// turning off, first try via switch (if exists), then dimmer
if (switch_id_.has_value()) {
parent_->set_datapoint_value(*this->switch_id_, false);
parent_->set_boolean_datapoint_value(*this->switch_id_, false);
} else if (dimmer_id_.has_value()) {
parent_->set_datapoint_value(*this->dimmer_id_, 0);
parent_->set_integer_datapoint_value(*this->dimmer_id_, 0);
}
return;
}
@@ -78,17 +78,17 @@ void TuyaLight::write_state(light::LightState *state) {
static_cast<uint32_t>(this->color_temperature_max_value_ *
(state->current_values.get_color_temperature() - this->cold_white_temperature_) /
(this->warm_white_temperature_ - this->cold_white_temperature_));
parent_->set_datapoint_value(*this->color_temperature_id_, color_temp_int);
parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int);
}
auto brightness_int = static_cast<uint32_t>(brightness * this->max_value_);
brightness_int = std::max(brightness_int, this->min_value_);
if (this->dimmer_id_.has_value()) {
parent_->set_datapoint_value(*this->dimmer_id_, brightness_int);
parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int);
}
if (this->switch_id_.has_value()) {
parent_->set_datapoint_value(*this->switch_id_, true);
parent_->set_boolean_datapoint_value(*this->switch_id_, true);
}
}

View File

@@ -15,7 +15,7 @@ void TuyaSwitch::setup() {
void TuyaSwitch::write_state(bool state) {
ESP_LOGV(TAG, "Setting switch %u: %s", this->switch_id_, ONOFF(state));
this->parent_->set_datapoint_value(this->switch_id_, state);
this->parent_->set_boolean_datapoint_value(this->switch_id_, state);
this->publish_state(state);
}

View File

@@ -297,7 +297,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
break;
default:
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, datapoint.type);
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
return;
}
@@ -437,42 +437,38 @@ void Tuya::send_local_time_() {
}
#endif
void Tuya::set_datapoint_value(uint8_t datapoint_id, uint32_t value) {
ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value);
void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str());
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id);
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
} else if (datapoint->type != TuyaDatapointType::RAW) {
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
return;
}
if (datapoint->value_uint == value) {
} else if (datapoint->value_raw == value) {
ESP_LOGV(TAG, "Not sending unchanged value");
return;
}
std::vector<uint8_t> data;
switch (datapoint->len) {
case 4:
data.push_back(value >> 24);
data.push_back(value >> 16);
case 2:
data.push_back(value >> 8);
case 1:
data.push_back(value >> 0);
break;
default:
ESP_LOGE(TAG, "Unexpected datapoint length %zu", datapoint->len);
return;
}
this->send_datapoint_command_(datapoint->id, datapoint->type, data);
this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value);
}
void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) {
void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1);
}
void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4);
}
void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str());
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id);
}
if (datapoint->value_string == value) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
} else if (datapoint->type != TuyaDatapointType::STRING) {
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
return;
} else if (datapoint->value_string == value) {
ESP_LOGV(TAG, "Not sending unchanged value");
return;
}
@@ -483,6 +479,14 @@ void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) {
this->send_datapoint_command_(datapoint->id, datapoint->type, data);
}
void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1);
}
void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length);
}
optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) {
for (auto &datapoint : this->datapoints_)
if (datapoint.id == datapoint_id)
@@ -490,6 +494,37 @@ optional<TuyaDatapoint> Tuya::get_datapoint_(uint8_t datapoint_id) {
return {};
}
void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value,
uint8_t length) {
ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value);
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
} else if (datapoint->type != datapoint_type) {
ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
return;
} else if (datapoint->value_uint == value) {
ESP_LOGV(TAG, "Not sending unchanged value");
return;
}
std::vector<uint8_t> data;
switch (length) {
case 4:
data.push_back(value >> 24);
data.push_back(value >> 16);
case 2:
data.push_back(value >> 8);
case 1:
data.push_back(value >> 0);
break;
default:
ESP_LOGE(TAG, "Unexpected datapoint length %u", length);
return;
}
this->send_datapoint_command_(datapoint_id, datapoint_type, data);
}
void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) {
std::vector<uint8_t> buffer;
buffer.push_back(datapoint_id);

View File

@@ -17,7 +17,7 @@ enum class TuyaDatapointType : uint8_t {
INTEGER = 0x02, // 4 byte
STRING = 0x03, // variable length
ENUM = 0x04, // 1 byte
BITMASK = 0x05, // 2 bytes
BITMASK = 0x05, // 1/2/4 bytes
};
struct TuyaDatapoint {
@@ -75,8 +75,12 @@ class Tuya : public Component, public uart::UARTDevice {
void loop() override;
void dump_config() override;
void register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func);
void set_datapoint_value(uint8_t datapoint_id, uint32_t value);
void set_datapoint_value(uint8_t datapoint_id, const std::string &value);
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value);
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value);
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value);
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value);
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value);
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length);
#ifdef USE_TIME
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
#endif
@@ -95,6 +99,8 @@ class Tuya : public Component, public uart::UARTDevice {
void process_command_queue_();
void send_command_(const TuyaCommand &command);
void send_empty_command_(TuyaCommandType command);
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value,
uint8_t length);
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data);
void send_wifi_status_();

View File

@@ -43,6 +43,9 @@ WaveshareEPaper7P5In = waveshare_epaper_ns.class_(
WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_(
"WaveshareEPaper7P5InV2", WaveshareEPaper
)
WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InDKE", WaveshareEPaper
)
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel")
@@ -64,13 +67,14 @@ MODELS = {
"5.83in": ("b", WaveshareEPaper5P8In),
"7.50in": ("b", WaveshareEPaper7P5In),
"7.50inv2": ("b", WaveshareEPaper7P5InV2),
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
}
def validate_full_update_every_only_type_a(value):
if CONF_FULL_UPDATE_EVERY not in value:
return value
if MODELS[value[CONF_MODEL]][0] != "a":
if MODELS[value[CONF_MODEL]][0] == "b":
raise cv.Invalid(
"The 'full_update_every' option is only available for models "
"'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'."
@@ -101,7 +105,7 @@ async def to_code(config):
if model_type == "a":
rhs = WaveshareEPaperTypeA.new(model)
var = cg.Pvariable(config[CONF_ID], rhs, WaveshareEPaperTypeA)
elif model_type == "b":
elif model_type in ("b", "c"):
rhs = model.new()
var = cg.Pvariable(config[CONF_ID], rhs, model)
else:

View File

@@ -1080,5 +1080,136 @@ void WaveshareEPaper7P5InV2::dump_config() {
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153;
static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = {
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
// 0x22, 0x17, 0x41, 0x0, 0x32, 0x32
};
void WaveshareEPaper2P13InDKE::initialize() {}
void HOT WaveshareEPaper2P13InDKE::display() {
bool partial = this->at_update_ != 0;
this->at_update_ = (this->at_update_ + 1) % this->full_update_every_;
if (partial)
ESP_LOGI(TAG, "Performing partial e-paper update.");
else
ESP_LOGI(TAG, "Performing full e-paper update.");
// start and set up data format
this->command(0x12);
this->wait_until_idle_();
this->command(0x11);
this->data(0x03);
this->command(0x44);
this->data(1);
this->data(this->get_width_internal() / 8);
this->command(0x45);
this->data(0);
this->data(0);
this->data(this->get_height_internal());
this->data(0);
this->command(0x4e);
this->data(1);
this->command(0x4f);
this->data(0);
this->data(0);
if (!partial) {
// send data
this->command(0x24);
this->start_data_();
this->write_array(this->buffer_, this->get_buffer_length_());
this->end_data_();
// commit
this->command(0x20);
this->wait_until_idle_();
} else {
// set up partial update
this->command(0x32);
for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE)
this->data(v);
this->command(0x3F);
this->data(0x22);
this->command(0x03);
this->data(0x17);
this->command(0x04);
this->data(0x41);
this->data(0x00);
this->data(0x32);
this->command(0x2C);
this->data(0x32);
this->command(0x37);
this->data(0x00);
this->data(0x00);
this->data(0x00);
this->data(0x00);
this->data(0x00);
this->data(0x40);
this->data(0x00);
this->data(0x00);
this->data(0x00);
this->data(0x00);
this->command(0x3C);
this->data(0x80);
this->command(0x22);
this->data(0xC0);
this->command(0x20);
this->wait_until_idle_();
// send data
this->command(0x24);
this->start_data_();
this->write_array(this->buffer_, this->get_buffer_length_());
this->end_data_();
// commit as partial
this->command(0x22);
this->data(0xCF);
this->command(0x20);
this->wait_until_idle_();
// data must be sent again on partial update
delay(300); // NOLINT
this->command(0x24);
this->start_data_();
this->write_array(this->buffer_, this->get_buffer_length_());
this->end_data_();
delay(300); // NOLINT
}
ESP_LOGI(TAG, "Completed e-paper update.");
}
int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; }
int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; }
int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; }
void WaveshareEPaper2P13InDKE::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 2.13inDKE");
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
void WaveshareEPaper2P13InDKE::set_full_update_every(uint32_t full_update_every) {
this->full_update_every_ = full_update_every;
}
} // namespace waveshare_epaper
} // namespace esphome

View File

@@ -301,5 +301,33 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper {
int get_height_internal() override;
};
class WaveshareEPaper2P13InDKE : public WaveshareEPaper {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND POWER DOWN
this->command(0x10);
this->data(0x01);
// cannot wait until idle here, the device no longer responds
}
void set_full_update_every(uint32_t full_update_every);
protected:
int get_width_internal() override;
int get_height_internal() override;
int idle_timeout_() override;
uint32_t full_update_every_{30};
uint32_t at_update_{0};
};
} // namespace waveshare_epaper
} // namespace esphome

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "1.21.0-dev"
__version__ = "2021.8.2"
ESP_PLATFORM_ESP32 = "ESP32"
ESP_PLATFORM_ESP8266 = "ESP8266"
@@ -163,6 +163,7 @@ CONF_DATA_TEMPLATE = "data_template"
CONF_DAYS_OF_MONTH = "days_of_month"
CONF_DAYS_OF_WEEK = "days_of_week"
CONF_DC_PIN = "dc_pin"
CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
CONF_DEBOUNCE = "debounce"
CONF_DECELERATION = "deceleration"
CONF_DEFAULT_MODE = "default_mode"
@@ -839,6 +840,9 @@ STATE_CLASS_NONE = ""
# The state represents a measurement in present time
STATE_CLASS_MEASUREMENT = "measurement"
# The state represents a total that only increases, a decrease is considered a reset.
STATE_CLASS_TOTAL_INCREASING = "total_increasing"
# This sensor does not support resetting. ie, it is not accumulative
LAST_RESET_TYPE_NONE = ""
# This sensor is expected to never reset its value

View File

@@ -98,7 +98,7 @@ class DashboardSettings:
return os.path.join(self.config_dir, *args)
def list_yaml_files(self):
return util.list_yaml_files(self.config_dir)
return util.list_yaml_files([self.config_dir])
settings = DashboardSettings()

View File

@@ -247,17 +247,24 @@ class OrderedDict(collections.OrderedDict):
return dict(self).__repr__()
def list_yaml_files(folder):
files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)])
def list_yaml_files(folders):
files = filter_yaml_files(
[os.path.join(folder, p) for folder in folders for p in os.listdir(folder)]
)
files.sort()
return files
def filter_yaml_files(files):
files = [f for f in files if os.path.splitext(f)[1] == ".yaml"]
files = [f for f in files if os.path.basename(f) != "secrets.yaml"]
files = [f for f in files if not os.path.basename(f).startswith(".")]
return files
return [
f
for f in files
if (
os.path.splitext(f)[1] == ".yaml"
and os.path.basename(f) != "secrets.yaml"
and not os.path.basename(f).startswith(".")
)
]
class SerialPort:

View File

@@ -67,7 +67,7 @@ def read_config(args):
CORE.ace = args.ace
f = data["file"]
if CORE.ace:
CORE.config_path = os.path.join(args.configuration[0], f)
CORE.config_path = os.path.join(args.configuration, f)
else:
CORE.config_path = data["file"]
vs = VSCodeResult()

View File

@@ -21,7 +21,7 @@ PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME)
GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH)
DOWNLOAD_URL = "{}/archive/v{}.zip".format(GITHUB_URL, const.__version__)
DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__)
here = os.path.abspath(os.path.dirname(__file__))

View File

@@ -2145,6 +2145,10 @@ cover:
id: template_cover
state: CLOSED
assumed_state: no
- platform: am43
name: 'Test AM43'
id: am43_test
ble_client_id: ble_foo
debug:

View File

@@ -56,6 +56,9 @@ time:
tuya:
time_id: sntp_time
pipsolar:
id: inverter0
sensor:
- platform: homeassistant
entity_id: sensor.hello_world
@@ -63,6 +66,140 @@ sensor:
- platform: tuya
id: tuya_sensor
sensor_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
grid_rating_voltage:
id: inverter0_grid_rating_voltage
name: inverter0_grid_rating_voltage
grid_rating_current:
id: inverter0_grid_rating_current
name: inverter0_grid_rating_current
ac_output_rating_voltage:
id: inverter0_ac_output_rating_voltage
name: inverter0_ac_output_rating_voltage
ac_output_rating_frequency:
id: inverter0_ac_output_rating_frequency
name: inverter0_ac_output_rating_frequency
ac_output_rating_current:
id: inverter0_ac_output_rating_current
name: inverter0_ac_output_rating_current
ac_output_rating_apparent_power:
id: inverter0_ac_output_rating_apparent_power
name: inverter0_ac_output_rating_apparent_power
ac_output_rating_active_power:
id: inverter0_ac_output_rating_active_power
name: inverter0_ac_output_rating_active_power
battery_rating_voltage:
id: inverter0_battery_rating_voltage
name: inverter0_battery_rating_voltage
battery_recharge_voltage:
id: inverter0_battery_recharge_voltage
name: inverter0_battery_recharge_voltage
battery_under_voltage:
id: inverter0_battery_under_voltage
name: inverter0_battery_under_voltage
battery_bulk_voltage:
id: inverter0_battery_bulk_voltage
name: inverter0_battery_bulk_voltage
battery_float_voltage:
id: inverter0_battery_float_voltage
name: inverter0_battery_float_voltage
battery_type:
id: inverter0_battery_type
name: inverter0_battery_type
current_max_ac_charging_current:
id: inverter0_current_max_ac_charging_current
name: inverter0_current_max_ac_charging_current
current_max_charging_current:
id: inverter0_current_max_charging_current
name: inverter0_current_max_charging_current
input_voltage_range:
id: inverter0_input_voltage_range
name: inverter0_input_voltage_range
output_source_priority:
id: inverter0_output_source_priority
name: inverter0_output_source_priority
charger_source_priority:
id: inverter0_charger_source_priority
name: inverter0_charger_source_priority
parallel_max_num:
id: inverter0_parallel_max_num
name: inverter0_parallel_max_num
machine_type:
id: inverter0_machine_type
name: inverter0_machine_type
topology:
id: inverter0_topology
name: inverter0_topology
output_mode:
id: inverter0_output_mode
name: inverter0_output_mode
battery_redischarge_voltage:
id: inverter0_battery_redischarge_voltage
name: inverter0_battery_redischarge_voltage
pv_ok_condition_for_parallel:
id: inverter0_pv_ok_condition_for_parallel
name: inverter0_pv_ok_condition_for_parallel
pv_power_balance:
id: inverter0_pv_power_balance
name: inverter0_pv_power_balance
grid_voltage:
id: inverter0_grid_voltage
name: inverter0_grid_voltage
grid_frequency:
id: inverter0_grid_frequency
name: inverter0_grid_frequency
ac_output_voltage:
id: inverter0_ac_output_voltage
name: inverter0_ac_output_voltage
ac_output_frequency:
id: inverter0_ac_output_frequency
name: inverter0_ac_output_frequency
ac_output_apparent_power:
id: inverter0_ac_output_apparent_power
name: inverter0_ac_output_apparent_power
ac_output_active_power:
id: inverter0_ac_output_active_power
name: inverter0_ac_output_active_power
output_load_percent:
id: inverter0_output_load_percent
name: inverter0_output_load_percent
bus_voltage:
id: inverter0_bus_voltage
name: inverter0_bus_voltage
battery_voltage:
id: inverter0_battery_voltage
name: inverter0_battery_voltage
battery_charging_current:
id: inverter0_battery_charging_current
name: inverter0_battery_charging_current
battery_capacity_percent:
id: inverter0_battery_capacity_percent
name: inverter0_battery_capacity_percent
inverter_heat_sink_temperature:
id: inverter0_inverter_heat_sink_temperature
name: inverter0_inverter_heat_sink_temperature
pv_input_current_for_battery:
id: inverter0_pv_input_current_for_battery
name: inverter0_pv_input_current_for_battery
pv_input_voltage:
id: inverter0_pv_input_voltage
name: inverter0_pv_input_voltage
battery_voltage_scc:
id: inverter0_battery_voltage_scc
name: inverter0_battery_voltage_scc
battery_discharge_current:
id: inverter0_battery_discharge_current
name: inverter0_battery_discharge_current
battery_voltage_offset_for_fans_on:
id: inverter0_battery_voltage_offset_for_fans_on
name: inverter0_battery_voltage_offset_for_fans_on
eeprom_version:
id: inverter0_eeprom_version
name: inverter0_eeprom_version
pv_charging_power:
id: inverter0_pv_charging_power
name: inverter0_pv_charging_power
- platform: "hrxl_maxsonar_wr"
name: "Rainwater Tank Level"
filters:
@@ -95,6 +232,59 @@ binary_sensor:
- platform: tuya
id: tuya_binary_sensor
sensor_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
add_sbu_priority_version:
id: inverter0_add_sbu_priority_version
name: inverter0_add_sbu_priority_version
configuration_status:
id: inverter0_configuration_status
name: inverter0_configuration_status
scc_firmware_version:
id: inverter0_scc_firmware_version
name: inverter0_scc_firmware_version
load_status:
id: inverter0_load_status
name: inverter0_load_status
battery_voltage_to_steady_while_charging:
id: inverter0_battery_voltage_to_steady_while_charging
name: inverter0_battery_voltage_to_steady_while_charging
charging_status:
id: inverter0_charging_status
name: inverter0_charging_status
scc_charging_status:
id: inverter0_scc_charging_status
name: inverter0_scc_charging_status
ac_charging_status:
id: inverter0_ac_charging_status
name: inverter0_ac_charging_status
charging_to_floating_mode:
id: inverter0_charging_to_floating_mode
name: inverter0_charging_to_floating_mode
switch_on:
id: inverter0_switch_on
name: inverter0_switch_on
dustproof_installed:
id: inverter0_dustproof_installed
name: inverter0_dustproof_installed
silence_buzzer_open_buzzer:
id: inverter0_silence_buzzer_open_buzzer
name: inverter0_silence_buzzer_open_buzzer
overload_bypass_function:
id: inverter0_overload_bypass_function
name: inverter0_overload_bypass_function
lcd_escape_to_default:
id: inverter0_lcd_escape_to_default
name: inverter0_lcd_escape_to_default
overload_restart_function:
id: inverter0_overload_restart_function
name: inverter0_overload_restart_function
over_temperature_restart_function:
id: inverter0_over_temperature_restart_function
name: inverter0_over_temperature_restart_function
backlight_on:
id: inverter0_backlight_on
name: inverter0_backlight_on
- platform: template
id: ar1
lambda: 'return {};'
@@ -131,6 +321,20 @@ switch:
- platform: tuya
id: tuya_switch
switch_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
output_source_priority_utility:
name: inverter0_output_source_priority_utility
output_source_priority_solar:
name: inverter0_output_source_priority_solar
output_source_priority_battery:
name: inverter0_output_source_priority_battery
input_voltage_range:
name: inverter0_input_voltage_range
pv_ok_condition_for_parallel:
name: inverter0_pv_ok_condition_for_parallel
pv_power_balance:
name: inverter0_pv_power_balance
light:
- platform: fastled_clockless
@@ -204,6 +408,30 @@ display:
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
text_sensor:
- platform: pipsolar
pipsolar_id: inverter0
device_mode:
id: inverter0_device_mode
name: inverter0_device_mode
last_qpigs:
id: inverter0_last_qpigs
name: inverter0_last_qpigs
last_qpiri:
id: inverter0_last_qpiri
name: inverter0_last_qpiri
last_qmod:
id: inverter0_last_qmod
name: inverter0_last_qmod
last_qflag:
id: inverter0_last_qflag
name: inverter0_last_qflag
output:
- platform: pipsolar
pipsolar_id: inverter0
battery_recharge_voltage:
id: inverter0_battery_recharge_voltage_out
esp32_camera:
name: ESP-32 Camera
data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19]