esphome 2024.12.3__py3-none-any.whl → 2025.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. esphome/__main__.py +16 -3
  2. esphome/components/adc/__init__.py +17 -11
  3. esphome/components/adc/adc_sensor.h +17 -0
  4. esphome/components/adc/adc_sensor_common.cpp +55 -0
  5. esphome/components/adc/adc_sensor_esp32.cpp +8 -5
  6. esphome/components/adc/adc_sensor_esp8266.cpp +10 -6
  7. esphome/components/adc/adc_sensor_libretiny.cpp +11 -6
  8. esphome/components/adc/adc_sensor_rp2040.cpp +13 -10
  9. esphome/components/adc/sensor.py +9 -3
  10. esphome/components/ads1115/ads1115.cpp +56 -7
  11. esphome/components/ads1115/ads1115.h +13 -1
  12. esphome/components/ads1115/sensor/__init__.py +16 -0
  13. esphome/components/ads1115/sensor/ads1115_sensor.cpp +2 -1
  14. esphome/components/ads1115/sensor/ads1115_sensor.h +2 -0
  15. esphome/components/animation/__init__.py +23 -261
  16. esphome/components/animation/animation.cpp +2 -2
  17. esphome/components/animation/animation.h +2 -1
  18. esphome/components/api/api_pb2.cpp +14 -0
  19. esphome/components/api/api_pb2.h +1 -0
  20. esphome/components/api/client.py +8 -3
  21. esphome/components/audio/__init__.py +112 -0
  22. esphome/components/audio/audio.cpp +67 -0
  23. esphome/components/audio/audio.h +125 -7
  24. esphome/components/audio/audio_decoder.cpp +361 -0
  25. esphome/components/audio/audio_decoder.h +135 -0
  26. esphome/components/audio/audio_reader.cpp +308 -0
  27. esphome/components/audio/audio_reader.h +85 -0
  28. esphome/components/audio/audio_resampler.cpp +159 -0
  29. esphome/components/audio/audio_resampler.h +101 -0
  30. esphome/components/audio/audio_transfer_buffer.cpp +165 -0
  31. esphome/components/audio/audio_transfer_buffer.h +139 -0
  32. esphome/components/audio_adc/__init__.py +41 -0
  33. esphome/components/audio_adc/audio_adc.h +17 -0
  34. esphome/components/audio_adc/automation.h +23 -0
  35. esphome/components/bk72xx/__init__.py +1 -0
  36. esphome/components/ble_client/ble_client.cpp +1 -2
  37. esphome/components/ble_client/sensor/__init__.py +1 -1
  38. esphome/components/ble_client/text_sensor/__init__.py +1 -1
  39. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +5 -0
  40. esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -0
  41. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +5 -0
  42. esphome/components/ch422g/ch422g.h +2 -0
  43. esphome/components/climate/__init__.py +1 -1
  44. esphome/components/climate_ir/climate_ir.cpp +2 -1
  45. esphome/components/coolix/coolix.cpp +2 -1
  46. esphome/components/cse7766/cse7766.cpp +8 -16
  47. esphome/components/custom/__init__.py +0 -3
  48. esphome/components/custom/binary_sensor/__init__.py +2 -28
  49. esphome/components/custom/climate/__init__.py +2 -27
  50. esphome/components/custom/cover/__init__.py +2 -27
  51. esphome/components/custom/light/__init__.py +2 -27
  52. esphome/components/custom/output/__init__.py +2 -58
  53. esphome/components/custom/sensor/__init__.py +2 -24
  54. esphome/components/custom/switch/__init__.py +2 -24
  55. esphome/components/custom/text_sensor/__init__.py +2 -29
  56. esphome/components/custom_component/__init__.py +3 -27
  57. esphome/components/daly_bms/daly_bms.cpp +6 -0
  58. esphome/components/daly_bms/daly_bms.h +2 -0
  59. esphome/components/daly_bms/sensor.py +6 -0
  60. esphome/components/debug/debug_component.cpp +4 -0
  61. esphome/components/debug/debug_component.h +14 -0
  62. esphome/components/debug/debug_esp32.cpp +154 -74
  63. esphome/components/dfplayer/dfplayer.cpp +15 -2
  64. esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp +2 -1
  65. esphome/components/dht/dht.cpp +4 -2
  66. esphome/components/dht/sensor.py +1 -1
  67. esphome/components/display/__init__.py +18 -5
  68. esphome/components/display/display.cpp +16 -3
  69. esphome/components/display/rect.cpp +2 -1
  70. esphome/components/es7210/__init__.py +0 -0
  71. esphome/components/es7210/audio_adc.py +51 -0
  72. esphome/components/es7210/es7210.cpp +228 -0
  73. esphome/components/es7210/es7210.h +62 -0
  74. esphome/components/es7210/es7210_const.h +129 -0
  75. esphome/components/es7243e/__init__.py +0 -0
  76. esphome/components/es7243e/audio_adc.py +34 -0
  77. esphome/components/es7243e/es7243e.cpp +125 -0
  78. esphome/components/es7243e/es7243e.h +37 -0
  79. esphome/components/es7243e/es7243e_const.h +54 -0
  80. esphome/components/es8156/__init__.py +0 -0
  81. esphome/components/es8156/audio_dac.py +27 -0
  82. esphome/components/es8156/es8156.cpp +87 -0
  83. esphome/components/es8156/es8156.h +51 -0
  84. esphome/components/es8156/es8156_const.h +68 -0
  85. esphome/components/es8311/audio_dac.py +1 -2
  86. esphome/components/esp32/__init__.py +1 -0
  87. esphome/components/esp32/core.cpp +5 -1
  88. esphome/components/esp32/gpio.h +2 -0
  89. esphome/components/esp32_ble/__init__.py +39 -0
  90. esphome/components/esp32_ble/queue.h +4 -4
  91. esphome/components/esp32_ble_client/ble_client_base.cpp +46 -0
  92. esphome/components/esp32_ble_client/ble_client_base.h +2 -0
  93. esphome/components/esp32_ble_server/__init__.py +582 -12
  94. esphome/components/esp32_ble_server/ble_characteristic.cpp +48 -60
  95. esphome/components/esp32_ble_server/ble_characteristic.h +24 -17
  96. esphome/components/esp32_ble_server/ble_descriptor.cpp +21 -9
  97. esphome/components/esp32_ble_server/ble_descriptor.h +17 -6
  98. esphome/components/esp32_ble_server/ble_server.cpp +62 -67
  99. esphome/components/esp32_ble_server/ble_server.h +28 -32
  100. esphome/components/esp32_ble_server/ble_server_automations.cpp +77 -0
  101. esphome/components/esp32_ble_server/ble_server_automations.h +115 -0
  102. esphome/components/esp32_ble_server/ble_service.cpp +17 -15
  103. esphome/components/esp32_ble_server/ble_service.h +10 -14
  104. esphome/components/esp32_ble_tracker/__init__.py +6 -39
  105. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +33 -10
  106. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +8 -4
  107. esphome/components/esp32_dac/esp32_dac.cpp +16 -7
  108. esphome/components/esp32_dac/esp32_dac.h +8 -0
  109. esphome/components/esp32_dac/output.py +16 -4
  110. esphome/components/esp32_improv/__init__.py +2 -8
  111. esphome/components/esp32_improv/esp32_improv_component.cpp +21 -20
  112. esphome/components/esp32_improv/esp32_improv_component.h +3 -4
  113. esphome/components/esp32_rmt/__init__.py +28 -3
  114. esphome/components/esp32_rmt_led_strip/led_strip.cpp +73 -6
  115. esphome/components/esp32_rmt_led_strip/led_strip.h +21 -3
  116. esphome/components/esp32_rmt_led_strip/light.py +72 -7
  117. esphome/components/esp32_touch/esp32_touch.cpp +5 -0
  118. esphome/components/esp8266/__init__.py +1 -0
  119. esphome/components/esp8266/gpio.h +1 -0
  120. esphome/components/ethernet/__init__.py +10 -10
  121. esphome/components/event/event.cpp +4 -2
  122. esphome/components/event/event.h +2 -0
  123. esphome/components/event_emitter/__init__.py +5 -0
  124. esphome/components/event_emitter/event_emitter.cpp +14 -0
  125. esphome/components/event_emitter/event_emitter.h +63 -0
  126. esphome/components/font/__init__.py +1 -1
  127. esphome/components/gcja5/gcja5.cpp +2 -1
  128. esphome/components/graph/graph.cpp +4 -9
  129. esphome/components/haier/haier_base.cpp +2 -1
  130. esphome/components/haier/hon_climate.cpp +2 -1
  131. esphome/components/heatpumpir/heatpumpir.cpp +2 -1
  132. esphome/components/host/__init__.py +1 -0
  133. esphome/components/host/gpio.h +1 -0
  134. esphome/components/http_request/http_request.h +2 -2
  135. esphome/components/http_request/http_request_arduino.cpp +1 -1
  136. esphome/components/http_request/http_request_idf.cpp +1 -1
  137. esphome/components/i2c/i2c_bus_esp_idf.cpp +4 -0
  138. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +7 -5
  139. esphome/components/i2s_audio/speaker/__init__.py +53 -6
  140. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +92 -46
  141. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +8 -0
  142. esphome/components/ili9xxx/display.py +29 -11
  143. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -5
  144. esphome/components/ili9xxx/ili9xxx_display.h +2 -1
  145. esphome/components/image/__init__.py +443 -255
  146. esphome/components/image/image.cpp +115 -61
  147. esphome/components/image/image.h +15 -24
  148. esphome/components/json/json_util.cpp +8 -34
  149. esphome/components/libretiny/__init__.py +1 -0
  150. esphome/components/libretiny/gpio_arduino.h +1 -0
  151. esphome/components/light/light_color_values.h +1 -1
  152. esphome/components/logger/__init__.py +45 -9
  153. esphome/components/logger/logger.cpp +16 -14
  154. esphome/components/logger/logger.h +11 -7
  155. esphome/components/logger/select/__init__.py +29 -0
  156. esphome/components/logger/select/logger_level_select.cpp +27 -0
  157. esphome/components/logger/select/logger_level_select.h +15 -0
  158. esphome/components/lvgl/__init__.py +96 -73
  159. esphome/components/lvgl/automation.py +39 -7
  160. esphome/components/lvgl/defines.py +8 -2
  161. esphome/components/lvgl/lvgl_esphome.cpp +8 -15
  162. esphome/components/lvgl/lvgl_esphome.h +20 -5
  163. esphome/components/lvgl/schemas.py +25 -14
  164. esphome/components/lvgl/trigger.py +27 -3
  165. esphome/components/lvgl/widgets/dropdown.py +1 -1
  166. esphome/components/lvgl/widgets/keyboard.py +8 -1
  167. esphome/components/lvgl/widgets/meter.py +2 -1
  168. esphome/components/lvgl/widgets/msgbox.py +1 -1
  169. esphome/components/lvgl/widgets/obj.py +1 -12
  170. esphome/components/lvgl/widgets/page.py +37 -2
  171. esphome/components/lvgl/widgets/tabview.py +1 -1
  172. esphome/components/max6956/max6956.h +2 -0
  173. esphome/components/mcp23016/mcp23016.h +2 -0
  174. esphome/components/mcp23xxx_base/mcp23xxx_base.h +2 -0
  175. esphome/components/mdns/__init__.py +1 -1
  176. esphome/components/media_player/__init__.py +37 -8
  177. esphome/components/media_player/automation.h +11 -2
  178. esphome/components/media_player/media_player.cpp +8 -0
  179. esphome/components/media_player/media_player.h +8 -4
  180. esphome/components/micronova/switch/micronova_switch.cpp +4 -2
  181. esphome/components/midea/ac_automations.h +3 -1
  182. esphome/components/midea/air_conditioner.cpp +7 -5
  183. esphome/components/midea/air_conditioner.h +1 -1
  184. esphome/components/midea/climate.py +4 -2
  185. esphome/components/midea/ir_transmitter.h +36 -5
  186. esphome/components/mixer/__init__.py +0 -0
  187. esphome/components/mixer/speaker/__init__.py +172 -0
  188. esphome/components/mixer/speaker/automation.h +19 -0
  189. esphome/components/mixer/speaker/mixer_speaker.cpp +624 -0
  190. esphome/components/mixer/speaker/mixer_speaker.h +207 -0
  191. esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +7 -13
  192. esphome/components/mpr121/mpr121.h +2 -0
  193. esphome/components/mqtt/__init__.py +1 -1
  194. esphome/components/mqtt/mqtt_client.cpp +7 -1
  195. esphome/components/mqtt/mqtt_client.h +1 -1
  196. esphome/components/mqtt/mqtt_climate.cpp +2 -2
  197. esphome/components/network/ip_address.h +2 -0
  198. esphome/components/nextion/automation.h +17 -0
  199. esphome/components/nextion/display.py +42 -17
  200. esphome/components/nextion/nextion.cpp +4 -10
  201. esphome/components/nextion/nextion.h +89 -82
  202. esphome/components/nextion/nextion_commands.cpp +10 -10
  203. esphome/components/ntc/sensor.py +2 -4
  204. esphome/components/online_image/__init__.py +98 -46
  205. esphome/components/online_image/bmp_image.cpp +101 -0
  206. esphome/components/online_image/bmp_image.h +40 -0
  207. esphome/components/online_image/image_decoder.cpp +31 -2
  208. esphome/components/online_image/image_decoder.h +24 -15
  209. esphome/components/online_image/jpeg_image.cpp +92 -0
  210. esphome/components/online_image/jpeg_image.h +34 -0
  211. esphome/components/online_image/online_image.cpp +118 -58
  212. esphome/components/online_image/online_image.h +39 -9
  213. esphome/components/online_image/png_image.cpp +7 -3
  214. esphome/components/online_image/png_image.h +2 -1
  215. esphome/components/opentherm/__init__.py +73 -7
  216. esphome/components/opentherm/automation.h +25 -0
  217. esphome/components/opentherm/const.py +1 -0
  218. esphome/components/opentherm/generate.py +39 -6
  219. esphome/components/opentherm/hub.cpp +117 -79
  220. esphome/components/opentherm/hub.h +31 -15
  221. esphome/components/opentherm/opentherm.cpp +47 -23
  222. esphome/components/opentherm/opentherm.h +27 -6
  223. esphome/components/opentherm/opentherm_macros.h +11 -0
  224. esphome/components/opentherm/schema.py +78 -1
  225. esphome/components/opentherm/validate.py +7 -2
  226. esphome/components/pca6416a/pca6416a.h +2 -0
  227. esphome/components/pca9554/pca9554.h +2 -0
  228. esphome/components/pcf8574/pcf8574.h +2 -0
  229. esphome/components/preferences/__init__.py +2 -4
  230. esphome/components/preferences/syncer.h +10 -3
  231. esphome/components/prometheus/prometheus_handler.cpp +313 -0
  232. esphome/components/prometheus/prometheus_handler.h +48 -7
  233. esphome/components/psram/psram.cpp +8 -1
  234. esphome/components/pulse_counter/pulse_counter_sensor.cpp +14 -9
  235. esphome/components/pulse_counter/pulse_counter_sensor.h +4 -4
  236. esphome/components/pulse_meter/pulse_meter_sensor.cpp +2 -0
  237. esphome/components/qspi_dbi/__init__.py +3 -0
  238. esphome/components/qspi_dbi/display.py +74 -47
  239. esphome/components/qspi_dbi/models.py +245 -2
  240. esphome/components/qspi_dbi/qspi_dbi.cpp +9 -16
  241. esphome/components/qspi_dbi/qspi_dbi.h +2 -2
  242. esphome/components/remote_base/__init__.py +77 -25
  243. esphome/components/remote_base/remote_base.cpp +1 -1
  244. esphome/components/remote_base/remote_base.h +20 -2
  245. esphome/components/remote_base/toto_protocol.cpp +100 -0
  246. esphome/components/remote_base/toto_protocol.h +45 -0
  247. esphome/components/remote_receiver/__init__.py +55 -10
  248. esphome/components/remote_receiver/remote_receiver.h +36 -3
  249. esphome/components/remote_receiver/remote_receiver_esp32.cpp +145 -6
  250. esphome/components/remote_transmitter/__init__.py +62 -4
  251. esphome/components/remote_transmitter/remote_transmitter.h +21 -2
  252. esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +140 -4
  253. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +3 -3
  254. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +3 -3
  255. esphome/components/resampler/__init__.py +0 -0
  256. esphome/components/resampler/speaker/__init__.py +103 -0
  257. esphome/components/resampler/speaker/resampler_speaker.cpp +318 -0
  258. esphome/components/resampler/speaker/resampler_speaker.h +107 -0
  259. esphome/components/resistance/resistance_sensor.h +2 -3
  260. esphome/components/resistance/sensor.py +2 -9
  261. esphome/components/rotary_encoder/rotary_encoder.cpp +8 -4
  262. esphome/components/rp2040/__init__.py +1 -0
  263. esphome/components/rp2040/gpio.h +1 -0
  264. esphome/components/rtl87xx/__init__.py +2 -0
  265. esphome/components/scd30/sensor.py +1 -1
  266. esphome/components/sdl/binary_sensor.py +270 -0
  267. esphome/components/sdl/sdl_esphome.cpp +16 -0
  268. esphome/components/sdl/sdl_esphome.h +9 -0
  269. esphome/components/seeed_mr60bha2/binary_sensor.py +25 -0
  270. esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +26 -2
  271. esphome/components/seeed_mr60bha2/seeed_mr60bha2.h +9 -20
  272. esphome/components/seeed_mr60bha2/sensor.py +9 -1
  273. esphome/components/sn74hc165/sn74hc165.h +3 -0
  274. esphome/components/sn74hc595/sn74hc595.h +3 -0
  275. esphome/components/speaker/__init__.py +5 -4
  276. esphome/components/speaker/media_player/__init__.py +458 -0
  277. esphome/components/speaker/media_player/audio_pipeline.cpp +568 -0
  278. esphome/components/speaker/media_player/audio_pipeline.h +159 -0
  279. esphome/components/speaker/media_player/automation.h +26 -0
  280. esphome/components/speaker/media_player/speaker_media_player.cpp +577 -0
  281. esphome/components/speaker/media_player/speaker_media_player.h +160 -0
  282. esphome/components/speaker/speaker.h +20 -0
  283. esphome/components/spi/__init__.py +1 -5
  284. esphome/components/spi/spi.cpp +7 -1
  285. esphome/components/spi/spi.h +21 -2
  286. esphome/components/spi_led_strip/light.py +3 -5
  287. esphome/components/spi_led_strip/spi_led_strip.cpp +67 -0
  288. esphome/components/spi_led_strip/spi_led_strip.h +8 -60
  289. esphome/components/sprinkler/sprinkler.cpp +3 -1
  290. esphome/components/sx1509/sx1509_gpio_pin.h +2 -0
  291. esphome/components/tca9555/tca9555.h +2 -0
  292. esphome/components/toshiba/toshiba.cpp +2 -1
  293. esphome/components/tuya/light/tuya_light.cpp +4 -2
  294. esphome/components/uart/uart_component_esp32_arduino.cpp +2 -2
  295. esphome/components/uart/uart_component_esp_idf.cpp +2 -2
  296. esphome/components/udp/__init__.py +8 -2
  297. esphome/components/udp/udp_component.cpp +25 -56
  298. esphome/components/udp/udp_component.h +3 -0
  299. esphome/components/uponor_smatrix/sensor/__init__.py +14 -4
  300. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +5 -0
  301. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h +1 -0
  302. esphome/components/uptime/text_sensor/__init__.py +19 -0
  303. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +63 -0
  304. esphome/components/uptime/text_sensor/uptime_text_sensor.h +25 -0
  305. esphome/components/voice_assistant/voice_assistant.cpp +24 -14
  306. esphome/components/voice_assistant/voice_assistant.h +8 -0
  307. esphome/components/waveshare_epaper/display.py +22 -1
  308. esphome/components/waveshare_epaper/waveshare_213v3.cpp +9 -3
  309. esphome/components/waveshare_epaper/waveshare_epaper.cpp +1155 -44
  310. esphome/components/waveshare_epaper/waveshare_epaper.h +208 -7
  311. esphome/components/web_server/web_server.cpp +28 -6
  312. esphome/components/weikai/weikai.h +2 -0
  313. esphome/components/wifi/__init__.py +6 -6
  314. esphome/components/wifi/wifi_component.cpp +1 -1
  315. esphome/components/wifi/wifi_component_esp32_arduino.cpp +30 -1
  316. esphome/components/wireguard/__init__.py +2 -2
  317. esphome/components/xl9535/xl9535.h +2 -0
  318. esphome/components/xxtea/__init__.py +3 -0
  319. esphome/components/xxtea/xxtea.cpp +46 -0
  320. esphome/components/xxtea/xxtea.h +26 -0
  321. esphome/components/yashima/yashima.cpp +2 -1
  322. esphome/config.py +9 -5
  323. esphome/config_validation.py +55 -17
  324. esphome/const.py +7 -10
  325. esphome/core/__init__.py +6 -13
  326. esphome/core/base_automation.h +1 -0
  327. esphome/core/config.py +59 -72
  328. esphome/core/defines.h +9 -1
  329. esphome/core/gpio.h +7 -0
  330. esphome/core/helpers.cpp +19 -15
  331. esphome/core/helpers.h +57 -8
  332. esphome/core/log.h +9 -7
  333. esphome/cpp_generator.py +2 -2
  334. esphome/dashboard/web_server.py +1 -1
  335. esphome/espota2.py +3 -2
  336. esphome/loader.py +12 -4
  337. esphome/log.py +5 -7
  338. esphome/yaml_util.py +2 -2
  339. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/METADATA +14 -9
  340. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/RECORD +349 -300
  341. esphome/components/custom/binary_sensor/custom_binary_sensor.cpp +0 -16
  342. esphome/components/custom/binary_sensor/custom_binary_sensor.h +0 -26
  343. esphome/components/custom/climate/custom_climate.h +0 -22
  344. esphome/components/custom/cover/custom_cover.h +0 -21
  345. esphome/components/custom/light/custom_light_output.h +0 -24
  346. esphome/components/custom/output/custom_output.h +0 -37
  347. esphome/components/custom/sensor/custom_sensor.cpp +0 -16
  348. esphome/components/custom/sensor/custom_sensor.h +0 -24
  349. esphome/components/custom/switch/custom_switch.cpp +0 -16
  350. esphome/components/custom/switch/custom_switch.h +0 -24
  351. esphome/components/custom/text_sensor/custom_text_sensor.cpp +0 -16
  352. esphome/components/custom/text_sensor/custom_text_sensor.h +0 -26
  353. esphome/components/custom_component/custom_component.h +0 -28
  354. esphome/components/esp32_ble_server/ble_2901.cpp +0 -18
  355. esphome/components/esp32_ble_server/ble_2901.h +0 -19
  356. esphome/components/resistance_sampler/__init__.py +0 -6
  357. esphome/components/resistance_sampler/resistance_sampler.h +0 -10
  358. esphome/components/uptime/{sensor.py → sensor/__init__.py} +3 -3
  359. /esphome/components/uptime/{uptime_seconds_sensor.cpp → sensor/uptime_seconds_sensor.cpp} +0 -0
  360. /esphome/components/uptime/{uptime_seconds_sensor.h → sensor/uptime_seconds_sensor.h} +0 -0
  361. /esphome/components/uptime/{uptime_timestamp_sensor.cpp → sensor/uptime_timestamp_sensor.cpp} +0 -0
  362. /esphome/components/uptime/{uptime_timestamp_sensor.h → sensor/uptime_timestamp_sensor.h} +0 -0
  363. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/LICENSE +0 -0
  364. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/WHEEL +0 -0
  365. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/entry_points.txt +0 -0
  366. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,12 @@
1
1
  from typing import Any
2
2
 
3
+ import logging
4
+ from esphome import automation
3
5
  import esphome.codegen as cg
4
6
  import esphome.config_validation as cv
5
7
  from esphome import pins
6
8
  from esphome.components import sensor
7
- from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
9
+ from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID
8
10
  from . import const, schema, validate, generate
9
11
 
10
12
  CODEOWNERS = ["@olegtarasov"]
@@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
20
22
  CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
21
23
  CONF_DHW_BLOCK = "dhw_block"
22
24
  CONF_SYNC_MODE = "sync_mode"
23
- CONF_OPENTHERM_VERSION = "opentherm_version"
25
+ CONF_OPENTHERM_VERSION = "opentherm_version" # Deprecated, will be removed
26
+ CONF_BEFORE_SEND = "before_send"
27
+ CONF_BEFORE_PROCESS_RESPONSE = "before_process_response"
28
+
29
+ # Triggers
30
+ BeforeSendTrigger = generate.opentherm_ns.class_(
31
+ "BeforeSendTrigger",
32
+ automation.Trigger.template(generate.OpenthermData.operator("ref")),
33
+ )
34
+ BeforeProcessResponseTrigger = generate.opentherm_ns.class_(
35
+ "BeforeProcessResponseTrigger",
36
+ automation.Trigger.template(generate.OpenthermData.operator("ref")),
37
+ )
38
+
39
+ _LOGGER = logging.getLogger(__name__)
24
40
 
25
41
  CONFIG_SCHEMA = cv.All(
26
42
  cv.Schema(
@@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
36
52
  cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
37
53
  cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
38
54
  cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
39
- cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float,
55
+ cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, # Deprecated
56
+ cv.Optional(CONF_BEFORE_SEND): automation.validate_automation(
57
+ {
58
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger),
59
+ }
60
+ ),
61
+ cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation(
62
+ {
63
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
64
+ BeforeProcessResponseTrigger
65
+ ),
66
+ }
67
+ ),
40
68
  }
41
69
  )
42
70
  .extend(
@@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
44
72
  schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
45
73
  )
46
74
  )
75
+ .extend(
76
+ validate.create_entities_schema(
77
+ schema.SETTINGS, (lambda s: s.validation_schema)
78
+ )
79
+ )
47
80
  .extend(cv.COMPONENT_SCHEMA),
48
81
  cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
49
82
  )
@@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None:
60
93
  out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
61
94
  cg.add(var.set_out_pin(out_pin))
62
95
 
63
- non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN}
96
+ non_sensors = {
97
+ CONF_ID,
98
+ CONF_IN_PIN,
99
+ CONF_OUT_PIN,
100
+ CONF_BEFORE_SEND,
101
+ CONF_BEFORE_PROCESS_RESPONSE,
102
+ }
64
103
  input_sensors = []
104
+ settings = []
65
105
  for key, value in config.items():
66
106
  if key in non_sensors:
67
107
  continue
68
108
  if key in schema.INPUTS:
69
109
  input_sensor = await cg.get_variable(value)
70
- cg.add(
71
- getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
72
- )
110
+ cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
73
111
  input_sensors.append(key)
112
+ elif key in schema.SETTINGS:
113
+ if value == schema.SETTINGS[key].default_value:
114
+ continue
115
+ cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value))
116
+ settings.append(key)
74
117
  else:
118
+ if key == CONF_OPENTHERM_VERSION:
119
+ _LOGGER.warning(
120
+ "opentherm_version is deprecated and will be removed in esphome 2025.2.0\n"
121
+ "Please change to 'opentherm_version_controller'."
122
+ )
75
123
  cg.add(getattr(var, f"set_{key}")(value))
76
124
 
77
125
  if len(input_sensors) > 0:
@@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None:
81
129
  )
82
130
  generate.define_readers(const.INPUT_SENSOR, input_sensors)
83
131
  generate.add_messages(var, input_sensors, schema.INPUTS)
132
+
133
+ if len(settings) > 0:
134
+ generate.define_has_settings(settings, schema.SETTINGS)
135
+ generate.define_message_handler(const.SETTING, settings, schema.SETTINGS)
136
+ generate.define_setting_readers(const.SETTING, settings)
137
+ generate.add_messages(var, settings, schema.SETTINGS)
138
+
139
+ for conf in config.get(CONF_BEFORE_SEND, []):
140
+ trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
141
+ await automation.build_automation(
142
+ trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
143
+ )
144
+
145
+ for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []):
146
+ trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
147
+ await automation.build_automation(
148
+ trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
149
+ )
@@ -0,0 +1,25 @@
1
+ #pragma once
2
+
3
+ #include "esphome/core/automation.h"
4
+ #include "hub.h"
5
+ #include "opentherm.h"
6
+
7
+ namespace esphome {
8
+ namespace opentherm {
9
+
10
+ class BeforeSendTrigger : public Trigger<OpenthermData &> {
11
+ public:
12
+ BeforeSendTrigger(OpenthermHub *hub) {
13
+ hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); });
14
+ }
15
+ };
16
+
17
+ class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> {
18
+ public:
19
+ BeforeProcessResponseTrigger(OpenthermHub *hub) {
20
+ hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); });
21
+ }
22
+ };
23
+
24
+ } // namespace opentherm
25
+ } // namespace esphome
@@ -9,3 +9,4 @@ SWITCH = "switch"
9
9
  NUMBER = "number"
10
10
  OUTPUT = "output"
11
11
  INPUT_SENSOR = "input_sensor"
12
+ SETTING = "setting"
@@ -1,13 +1,14 @@
1
1
  from collections.abc import Awaitable
2
- from typing import Any, Callable
2
+ from typing import Any, Callable, Optional
3
3
 
4
4
  import esphome.codegen as cg
5
5
  from esphome.const import CONF_ID
6
6
  from . import const
7
- from .schema import TSchema
7
+ from .schema import TSchema, SettingSchema
8
8
 
9
9
  opentherm_ns = cg.esphome_ns.namespace("opentherm")
10
10
  OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
11
+ OpenthermData = opentherm_ns.class_("OpenthermData")
11
12
 
12
13
 
13
14
  def define_has_component(component_type: str, keys: list[str]) -> None:
@@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None:
21
22
  cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
22
23
 
23
24
 
25
+ # We need a separate set of macros for settings because there are different backing field types we need to take
26
+ # into account
27
+ def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None:
28
+ cg.add_define(
29
+ "OPENTHERM_SETTING_LIST(F, sep)",
30
+ cg.RawExpression(
31
+ " sep ".join(
32
+ map(
33
+ lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})",
34
+ keys,
35
+ )
36
+ )
37
+ ),
38
+ )
39
+ for key in keys:
40
+ cg.add_define(f"OPENTHERM_HAS_SETTING_{key}")
41
+
42
+
24
43
  def define_message_handler(
25
44
  component_type: str, keys: list[str], schemas: dict[str, TSchema]
26
45
  ) -> None:
@@ -74,16 +93,30 @@ def define_readers(component_type: str, keys: list[str]) -> None:
74
93
  )
75
94
 
76
95
 
96
+ def define_setting_readers(component_type: str, keys: list[str]) -> None:
97
+ for key in keys:
98
+ cg.add_define(
99
+ f"OPENTHERM_READ_{key}",
100
+ cg.RawExpression(f"this->{key}_{component_type.lower()}"),
101
+ )
102
+
103
+
77
104
  def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
78
- messages: set[tuple[str, bool]] = set()
105
+ messages: dict[str, tuple[bool, Optional[int]]] = {}
79
106
  for key in keys:
80
- messages.add((schemas[key].message, schemas[key].keep_updated))
81
- for msg, keep_updated in messages:
107
+ messages[schemas[key].message] = (
108
+ schemas[key].keep_updated,
109
+ schemas[key].order if hasattr(schemas[key], "order") else None,
110
+ )
111
+ for msg, (keep_updated, order) in messages.items():
82
112
  msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
83
113
  if keep_updated:
84
114
  cg.add(hub.add_repeating_message(msg_expr))
85
115
  else:
86
- cg.add(hub.add_initial_message(msg_expr))
116
+ if order is not None:
117
+ cg.add(hub.add_initial_message(msg_expr, order))
118
+ else:
119
+ cg.add(hub.add_initial_message(msg_expr))
87
120
 
88
121
 
89
122
  def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:
@@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
63
63
  OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
64
64
  OpenthermData data;
65
65
  data.type = 0;
66
- data.id = 0;
66
+ data.id = request_id;
67
67
  data.valueHB = 0;
68
68
  data.valueLB = 0;
69
69
 
@@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
82
82
  // NOLINTEND
83
83
 
84
84
  data.type = MessageType::READ_DATA;
85
- data.id = MessageId::STATUS;
86
85
  data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
87
86
  (summer_mode_is_active << 5) | (dhw_blocked << 6);
88
87
 
89
88
  return data;
90
89
  }
91
90
 
92
- // Another special case is OpenTherm version number which is configured at hub level as a constant
93
- if (request_id == MessageId::OT_VERSION_CONTROLLER) {
94
- data.type = MessageType::WRITE_DATA;
95
- data.id = MessageId::OT_VERSION_CONTROLLER;
96
- data.f88(this->opentherm_version_);
97
-
98
- return data;
99
- }
100
-
101
- // Disable incomplete switch statement warnings, because the cases in each
102
- // switch are generated based on the configured sensors and inputs.
103
- #pragma GCC diagnostic push
104
- #pragma GCC diagnostic ignored "-Wswitch"
105
-
106
- // Next, we start with the write requests from switches and other inputs,
91
+ // Next, we start with write requests from switches and other inputs,
107
92
  // because we would want to write that data if it is available, rather than
108
93
  // request a read for that type (in the case that both read and write are
109
94
  // supported).
@@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
116
101
  OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
117
102
  OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
118
103
  OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
104
+ OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, ,
105
+ OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
106
+ default:
107
+ break;
119
108
  }
120
109
 
121
110
  // Finally, handle the simple read requests, which only change with the message id.
122
- switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) }
111
+ switch (request_id) {
112
+ OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
113
+ default:
114
+ break;
115
+ }
123
116
  switch (request_id) {
124
117
  OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
118
+ default:
119
+ break;
125
120
  }
126
- #pragma GCC diagnostic pop
127
121
 
128
122
  // And if we get here, a message was requested which somehow wasn't handled.
129
123
  // This shouldn't happen due to the way the defines are configured, so we
@@ -163,18 +157,36 @@ void OpenthermHub::setup() {
163
157
  // communicate at least once every second. Sending the status request is
164
158
  // good practice anyway.
165
159
  this->add_repeating_message(MessageId::STATUS);
160
+ this->write_initial_messages_(this->messages_);
161
+ this->message_iterator_ = this->messages_.begin();
162
+ }
166
163
 
167
- // Also ensure that we start communication with the STATUS message
168
- this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS);
169
-
170
- if (this->opentherm_version_ > 0.0f) {
171
- this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER);
172
- }
164
+ void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
173
165
 
174
- this->current_message_iterator_ = this->initial_messages_.begin();
166
+ // Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
167
+ void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) { // NOLINT
168
+ std::vector<std::pair<MessageId, uint8_t>> sorted;
169
+ std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted),
170
+ [](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; });
171
+ std::sort(sorted.begin(), sorted.end(),
172
+ [](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) {
173
+ return a.second < b.second;
174
+ });
175
+
176
+ target.clear();
177
+ std::transform(sorted.begin(), sorted.end(), std::back_inserter(target),
178
+ [](const std::pair<MessageId, uint8_t> &pair) { return pair.first; });
175
179
  }
176
180
 
177
- void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
181
+ // Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
182
+ void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) { // NOLINT
183
+ target.clear();
184
+ for (auto const &pair : this->configured_messages_) {
185
+ if (pair.second == REPEATING_MESSAGE_ORDER) {
186
+ target.push_back(pair.first);
187
+ }
188
+ }
189
+ }
178
190
 
179
191
  void OpenthermHub::loop() {
180
192
  if (this->sync_mode_) {
@@ -184,29 +196,18 @@ void OpenthermHub::loop() {
184
196
 
185
197
  auto cur_time = millis();
186
198
  auto const cur_mode = this->opentherm_->get_mode();
199
+
200
+ if (this->handle_error_(cur_mode)) {
201
+ return;
202
+ }
203
+
187
204
  switch (cur_mode) {
188
205
  case OperationMode::WRITE:
189
206
  case OperationMode::READ:
190
207
  case OperationMode::LISTEN:
191
- if (!this->check_timings_(cur_time)) {
192
- break;
193
- }
194
- this->last_mode_ = cur_mode;
195
- break;
196
- case OperationMode::ERROR_PROTOCOL:
197
- if (this->last_mode_ == OperationMode::WRITE) {
198
- this->handle_protocol_write_error_();
199
- } else if (this->last_mode_ == OperationMode::READ) {
200
- this->handle_protocol_read_error_();
201
- }
202
-
203
- this->stop_opentherm_();
204
- break;
205
- case OperationMode::ERROR_TIMEOUT:
206
- this->handle_timeout_error_();
207
- this->stop_opentherm_();
208
208
  break;
209
209
  case OperationMode::IDLE:
210
+ this->check_timings_(cur_time);
210
211
  if (this->should_skip_loop_(cur_time)) {
211
212
  break;
212
213
  }
@@ -219,6 +220,28 @@ void OpenthermHub::loop() {
219
220
  case OperationMode::RECEIVED:
220
221
  this->read_response_();
221
222
  break;
223
+ default:
224
+ break;
225
+ }
226
+ this->last_mode_ = cur_mode;
227
+ }
228
+
229
+ bool OpenthermHub::handle_error_(OperationMode mode) {
230
+ switch (mode) {
231
+ case OperationMode::ERROR_PROTOCOL:
232
+ // Protocol error can happen only while reading boiler response.
233
+ this->handle_protocol_error_();
234
+ return true;
235
+ case OperationMode::ERROR_TIMEOUT:
236
+ // Timeout error might happen while we wait for device to respond.
237
+ this->handle_timeout_error_();
238
+ return true;
239
+ case OperationMode::ERROR_TIMER:
240
+ // Timer error can happen only on ESP32.
241
+ this->handle_timer_error_();
242
+ return true;
243
+ default:
244
+ return false;
222
245
  }
223
246
  }
224
247
 
@@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() {
237
260
  }
238
261
 
239
262
  this->start_conversation_();
263
+ // There may be a timer error at this point
264
+ if (this->handle_error_(this->opentherm_->get_mode())) {
265
+ return;
266
+ }
240
267
 
268
+ // Spin while message is being sent to device
241
269
  if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
242
270
  ESP_LOGE(TAG, "Hub timeout triggered during send");
243
271
  this->stop_opentherm_();
244
272
  return;
245
273
  }
246
274
 
247
- if (this->opentherm_->is_error()) {
248
- this->handle_protocol_write_error_();
249
- this->stop_opentherm_();
275
+ // Check for errors and ensure we are in the right state (message sent successfully)
276
+ if (this->handle_error_(this->opentherm_->get_mode())) {
250
277
  return;
251
278
  } else if (!this->opentherm_->is_sent()) {
252
279
  ESP_LOGW(TAG, "Unexpected state after sending request: %s",
@@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
257
284
 
258
285
  // Listen for the response
259
286
  this->opentherm_->listen();
287
+ // There may be a timer error at this point
288
+ if (this->handle_error_(this->opentherm_->get_mode())) {
289
+ return;
290
+ }
291
+
292
+ // Spin while response is being received
260
293
  if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
261
294
  ESP_LOGE(TAG, "Hub timeout triggered during receive");
262
295
  this->stop_opentherm_();
263
296
  return;
264
297
  }
265
298
 
266
- if (this->opentherm_->is_timeout()) {
267
- this->handle_timeout_error_();
268
- this->stop_opentherm_();
269
- return;
270
- } else if (this->opentherm_->is_protocol_error()) {
271
- this->handle_protocol_read_error_();
272
- this->stop_opentherm_();
299
+ // Check for errors and ensure we are in the right state (message received successfully)
300
+ if (this->handle_error_(this->opentherm_->get_mode())) {
273
301
  return;
274
302
  } else if (!this->opentherm_->has_message()) {
275
303
  ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
@@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
281
309
  this->read_response_();
282
310
  }
283
311
 
284
- bool OpenthermHub::check_timings_(uint32_t cur_time) {
312
+ void OpenthermHub::check_timings_(uint32_t cur_time) {
285
313
  if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
286
314
  ESP_LOGW(TAG,
287
315
  "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
288
316
  "components that might slow the loop down.",
289
317
  (int) (cur_time - this->last_conversation_start_));
290
- this->stop_opentherm_();
291
- return false;
292
318
  }
293
-
294
- return true;
295
319
  }
296
320
 
297
321
  bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
@@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
304
328
  }
305
329
 
306
330
  void OpenthermHub::start_conversation_() {
307
- if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) {
308
- this->sending_initial_ = false;
309
- this->current_message_iterator_ = this->repeating_messages_.begin();
310
- } else if (this->current_message_iterator_ == this->repeating_messages_.end()) {
311
- this->current_message_iterator_ = this->repeating_messages_.begin();
331
+ if (this->message_iterator_ == this->messages_.end()) {
332
+ if (this->sending_initial_) {
333
+ this->sending_initial_ = false;
334
+ this->write_repeating_messages_(this->messages_);
335
+ }
336
+ this->message_iterator_ = this->messages_.begin();
312
337
  }
313
338
 
314
- auto request = this->build_request_(*this->current_message_iterator_);
339
+ auto request = this->build_request_(*this->message_iterator_);
340
+
341
+ this->before_send_callback_.call(request);
315
342
 
316
343
  ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
317
344
  this->opentherm_->message_id_to_str((MessageId) request.id));
@@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
331
358
 
332
359
  this->stop_opentherm_();
333
360
 
361
+ this->before_process_response_callback_.call(response);
334
362
  this->process_response(response);
335
363
 
336
- this->current_message_iterator_++;
364
+ this->message_iterator_++;
337
365
  }
338
366
 
339
367
  void OpenthermHub::stop_opentherm_() {
340
368
  this->opentherm_->stop();
341
369
  this->last_conversation_end_ = millis();
342
370
  }
343
- void OpenthermHub::handle_protocol_write_error_() {
344
- ESP_LOGW(TAG, "Error while sending request: %s",
345
- this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
346
- this->opentherm_->debug_data(this->last_request_);
347
- }
348
- void OpenthermHub::handle_protocol_read_error_() {
371
+
372
+ void OpenthermHub::handle_protocol_error_() {
349
373
  OpenThermError error;
350
374
  this->opentherm_->get_protocol_error(error);
351
375
  ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
352
- this->opentherm_->protocol_error_to_to_str(error.error_type));
376
+ this->opentherm_->protocol_error_to_str(error.error_type));
353
377
  this->opentherm_->debug_error(error);
378
+ this->stop_opentherm_();
354
379
  }
380
+
355
381
  void OpenthermHub::handle_timeout_error_() {
356
- ESP_LOGW(TAG, "Receive response timed out at a protocol level");
382
+ ESP_LOGW(TAG, "Timeout while waiting for response from device");
357
383
  this->stop_opentherm_();
358
384
  }
359
385
 
386
+ void OpenthermHub::handle_timer_error_() {
387
+ this->opentherm_->report_and_reset_timer_error();
388
+ this->stop_opentherm_();
389
+ // Timer error is critical, there is no point in retrying.
390
+ this->mark_failed();
391
+ }
392
+
360
393
  void OpenthermHub::dump_config() {
394
+ std::vector<MessageId> initial_messages;
395
+ std::vector<MessageId> repeating_messages;
396
+ this->write_initial_messages_(initial_messages);
397
+ this->write_repeating_messages_(repeating_messages);
398
+
361
399
  ESP_LOGCONFIG(TAG, "OpenTherm:");
362
400
  LOG_PIN(" In: ", this->in_pin_);
363
401
  LOG_PIN(" Out: ", this->out_pin_);
364
- ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_);
402
+ ESP_LOGCONFIG(TAG, " Sync mode: %s", YESNO(this->sync_mode_));
365
403
  ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
366
404
  ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
367
405
  ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
@@ -369,12 +407,12 @@ void OpenthermHub::dump_config() {
369
407
  ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
370
408
  ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
371
409
  ESP_LOGCONFIG(TAG, " Initial requests:");
372
- for (auto type : this->initial_messages_) {
373
- ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
410
+ for (auto type : initial_messages) {
411
+ ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
374
412
  }
375
413
  ESP_LOGCONFIG(TAG, " Repeating requests:");
376
- for (auto type : this->repeating_messages_) {
377
- ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
414
+ for (auto type : repeating_messages) {
415
+ ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
378
416
  }
379
417
  }
380
418
 
@@ -38,6 +38,9 @@
38
38
  namespace esphome {
39
39
  namespace opentherm {
40
40
 
41
+ static const uint8_t REPEATING_MESSAGE_ORDER = 255;
42
+ static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
43
+
41
44
  // OpenTherm component for ESPHome
42
45
  class OpenthermHub : public Component {
43
46
  protected:
@@ -58,15 +61,12 @@ class OpenthermHub : public Component {
58
61
 
59
62
  OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
60
63
 
61
- // The set of initial messages to send on starting communication with the boiler
62
- std::vector<MessageId> initial_messages_;
63
- // and the repeating messages which are sent repeatedly to update various sensors
64
- // and boiler parameters (like the setpoint).
65
- std::vector<MessageId> repeating_messages_;
66
- // Indicates if we are still working on the initial requests or not
64
+ OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
65
+
67
66
  bool sending_initial_ = true;
68
- // Index for the current request in one of the _requests sets.
69
- std::vector<MessageId>::const_iterator current_message_iterator_;
67
+ std::unordered_map<MessageId, uint8_t> configured_messages_;
68
+ std::vector<MessageId> messages_;
69
+ std::vector<MessageId>::const_iterator message_iterator_;
70
70
 
71
71
  uint32_t last_conversation_start_ = 0;
72
72
  uint32_t last_conversation_end_ = 0;
@@ -78,20 +78,25 @@ class OpenthermHub : public Component {
78
78
  // Very likely to happen while using Dallas temperature sensors.
79
79
  bool sync_mode_ = false;
80
80
 
81
- float opentherm_version_ = 0.0f;
81
+ CallbackManager<void(OpenthermData &)> before_send_callback_;
82
+ CallbackManager<void(OpenthermData &)> before_process_response_callback_;
82
83
 
83
84
  // Create OpenTherm messages based on the message id
84
85
  OpenthermData build_request_(MessageId request_id) const;
85
- void handle_protocol_write_error_();
86
- void handle_protocol_read_error_();
86
+ bool handle_error_(OperationMode mode);
87
+ void handle_protocol_error_();
87
88
  void handle_timeout_error_();
89
+ void handle_timer_error_();
88
90
  void stop_opentherm_();
89
91
  void start_conversation_();
90
92
  void read_response_();
91
- bool check_timings_(uint32_t cur_time);
93
+ void check_timings_(uint32_t cur_time);
92
94
  bool should_skip_loop_(uint32_t cur_time) const;
93
95
  void sync_loop_();
94
96
 
97
+ void write_initial_messages_(std::vector<MessageId> &target);
98
+ void write_repeating_messages_(std::vector<MessageId> &target);
99
+
95
100
  template<typename F> bool spin_wait_(uint32_t timeout, F func) {
96
101
  auto start_time = millis();
97
102
  while (func()) {
@@ -127,13 +132,18 @@ class OpenthermHub : public Component {
127
132
 
128
133
  OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
129
134
 
135
+ OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
136
+
130
137
  // Add a request to the vector of initial requests
131
- void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); }
138
+ void add_initial_message(MessageId message_id) {
139
+ this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
140
+ }
141
+ void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
132
142
  // Add a request to the set of repeating requests. Note that a large number of repeating
133
143
  // requests will slow down communication with the boiler. Each request may take up to 1 second,
134
144
  // so with all sensors enabled, it may take about half a minute before a change in setpoint
135
145
  // will be processed.
136
- void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); }
146
+ void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
137
147
 
138
148
  // There are seven status variables, which can either be set as a simple variable,
139
149
  // or using a switch. ch_enable and dhw_enable default to true, the others to false.
@@ -149,7 +159,13 @@ class OpenthermHub : public Component {
149
159
  void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
150
160
  void set_dhw_block(bool value) { this->dhw_block = value; }
151
161
  void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
152
- void set_opentherm_version(float value) { this->opentherm_version_ = value; }
162
+
163
+ void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) {
164
+ this->before_send_callback_.add(std::move(callback));
165
+ }
166
+ void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) {
167
+ this->before_process_response_callback_.add(std::move(callback));
168
+ }
153
169
 
154
170
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
155
171