esphome 2025.2.1__py3-none-any.whl → 2025.3.0b1__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 (140) hide show
  1. esphome/__main__.py +9 -1
  2. esphome/components/api/api_connection.cpp +426 -70
  3. esphome/components/api/api_connection.h +117 -25
  4. esphome/components/api/api_pb2.cpp +9 -0
  5. esphome/components/api/api_pb2.h +1 -0
  6. esphome/components/api/api_server.cpp +2 -2
  7. esphome/components/api/list_entities.cpp +76 -22
  8. esphome/components/api/list_entities.h +1 -0
  9. esphome/components/api/subscribe_state.h +2 -0
  10. esphome/components/audio/audio_reader.cpp +7 -7
  11. esphome/components/audio/audio_reader.h +1 -1
  12. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -0
  13. esphome/components/bmp085/bmp085.cpp +1 -1
  14. esphome/components/chsc6x/__init__.py +2 -0
  15. esphome/components/chsc6x/chsc6x_touchscreen.cpp +47 -0
  16. esphome/components/chsc6x/chsc6x_touchscreen.h +34 -0
  17. esphome/components/chsc6x/touchscreen.py +33 -0
  18. esphome/components/climate/__init__.py +0 -1
  19. esphome/components/cst816/binary_sensor/__init__.py +2 -25
  20. esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +3 -14
  21. esphome/components/cst816/touchscreen/cst816_touchscreen.h +0 -4
  22. esphome/components/esp32_ble_beacon/__init__.py +3 -1
  23. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -2
  24. esphome/components/esp8266/gpio.py +1 -2
  25. esphome/components/font/__init__.py +185 -185
  26. esphome/components/font/font.cpp +4 -4
  27. esphome/components/font/font.h +1 -0
  28. esphome/components/haier/climate.py +11 -10
  29. esphome/components/hbridge/switch/hbridge_switch.cpp +2 -2
  30. esphome/components/heatpumpir/climate.py +2 -1
  31. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  32. esphome/components/heatpumpir/heatpumpir.h +1 -0
  33. esphome/components/i2c/__init__.py +6 -6
  34. esphome/components/i2c/i2c_bus_esp_idf.cpp +6 -2
  35. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  36. esphome/components/ili9xxx/display.py +1 -0
  37. esphome/components/ili9xxx/ili9xxx_display.h +5 -0
  38. esphome/components/ili9xxx/ili9xxx_init.h +59 -0
  39. esphome/components/ld2450/__init__.py +51 -0
  40. esphome/components/ld2450/binary_sensor.py +47 -0
  41. esphome/components/ld2450/button/__init__.py +45 -0
  42. esphome/components/ld2450/button/reset_button.cpp +9 -0
  43. esphome/components/ld2450/button/reset_button.h +18 -0
  44. esphome/components/ld2450/button/restart_button.cpp +9 -0
  45. esphome/components/ld2450/button/restart_button.h +18 -0
  46. esphome/components/ld2450/ld2450.cpp +876 -0
  47. esphome/components/ld2450/ld2450.h +234 -0
  48. esphome/components/ld2450/number/__init__.py +121 -0
  49. esphome/components/ld2450/number/presence_timeout_number.cpp +12 -0
  50. esphome/components/ld2450/number/presence_timeout_number.h +18 -0
  51. esphome/components/ld2450/number/zone_coordinate_number.cpp +14 -0
  52. esphome/components/ld2450/number/zone_coordinate_number.h +19 -0
  53. esphome/components/ld2450/select/__init__.py +56 -0
  54. esphome/components/ld2450/select/baud_rate_select.cpp +12 -0
  55. esphome/components/ld2450/select/baud_rate_select.h +18 -0
  56. esphome/components/ld2450/select/zone_type_select.cpp +12 -0
  57. esphome/components/ld2450/select/zone_type_select.h +18 -0
  58. esphome/components/ld2450/sensor.py +156 -0
  59. esphome/components/ld2450/switch/__init__.py +45 -0
  60. esphome/components/ld2450/switch/bluetooth_switch.cpp +12 -0
  61. esphome/components/ld2450/switch/bluetooth_switch.h +18 -0
  62. esphome/components/ld2450/switch/multi_target_switch.cpp +12 -0
  63. esphome/components/ld2450/switch/multi_target_switch.h +18 -0
  64. esphome/components/ld2450/text_sensor.py +62 -0
  65. esphome/components/ltr390/ltr390.cpp +7 -7
  66. esphome/components/ltr390/ltr390.h +0 -1
  67. esphome/components/lvgl/defines.py +0 -2
  68. esphome/components/lvgl/font.cpp +1 -1
  69. esphome/components/lvgl/lvgl_esphome.cpp +27 -19
  70. esphome/components/lvgl/widgets/img.py +1 -3
  71. esphome/components/mcp2515/mcp2515.cpp +1 -0
  72. esphome/components/mlx90393/sensor.py +53 -33
  73. esphome/components/mlx90393/sensor_mlx90393.cpp +4 -0
  74. esphome/components/mlx90393/sensor_mlx90393.h +8 -3
  75. esphome/components/mqtt/__init__.py +2 -2
  76. esphome/components/msa3xx/__init__.py +189 -0
  77. esphome/components/msa3xx/binary_sensor.py +40 -0
  78. esphome/components/msa3xx/msa3xx.cpp +417 -0
  79. esphome/components/msa3xx/msa3xx.h +311 -0
  80. esphome/components/msa3xx/sensor.py +42 -0
  81. esphome/components/msa3xx/text_sensor.py +38 -0
  82. esphome/components/nfc/binary_sensor/__init__.py +4 -4
  83. esphome/components/opentherm/binary_sensor/__init__.py +4 -4
  84. esphome/components/opentherm/generate.py +6 -6
  85. esphome/components/opentherm/sensor/__init__.py +5 -6
  86. esphome/components/packages/__init__.py +35 -11
  87. esphome/components/pn532/binary_sensor.py +4 -4
  88. esphome/components/rc522/binary_sensor.py +4 -4
  89. esphome/components/socket/bsd_sockets_impl.cpp +1 -0
  90. esphome/components/socket/lwip_sockets_impl.cpp +1 -0
  91. esphome/components/socket/socket.h +3 -1
  92. esphome/components/ssd1306_base/__init__.py +7 -7
  93. esphome/components/thermostat/climate.py +1 -1
  94. esphome/components/tmp1075/tmp1075.cpp +7 -11
  95. esphome/components/tmp1075/tmp1075.h +1 -2
  96. esphome/components/tormatic/__init__.py +1 -0
  97. esphome/components/tormatic/cover.py +47 -0
  98. esphome/components/tormatic/tormatic_cover.cpp +355 -0
  99. esphome/components/tormatic/tormatic_cover.h +60 -0
  100. esphome/components/tormatic/tormatic_protocol.h +211 -0
  101. esphome/components/touchscreen/binary_sensor/__init__.py +3 -0
  102. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +7 -1
  103. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +3 -1
  104. esphome/components/touchscreen/touchscreen.cpp +3 -4
  105. esphome/components/udp/udp_component.h +4 -1
  106. esphome/components/web_server/list_entities.cpp +70 -66
  107. esphome/components/web_server/list_entities.h +43 -22
  108. esphome/components/web_server/web_server.cpp +345 -68
  109. esphome/components/web_server/web_server.h +138 -6
  110. esphome/components/web_server_base/__init__.py +1 -1
  111. esphome/components/web_server_idf/__init__.py +2 -0
  112. esphome/components/web_server_idf/web_server_idf.cpp +177 -30
  113. esphome/components/web_server_idf/web_server_idf.h +53 -4
  114. esphome/config_validation.py +23 -125
  115. esphome/const.py +5 -1
  116. esphome/core/config.py +12 -4
  117. esphome/core/defines.h +1 -1
  118. esphome/core/helpers.h +5 -3
  119. esphome/core/time.cpp +1 -0
  120. esphome/cpp_generator.py +3 -3
  121. esphome/dashboard/core.py +30 -21
  122. esphome/dashboard/dns.py +7 -1
  123. esphome/dashboard/entries.py +83 -16
  124. esphome/dashboard/settings.py +0 -4
  125. esphome/dashboard/status/mdns.py +43 -14
  126. esphome/dashboard/status/mqtt.py +22 -9
  127. esphome/dashboard/status/ping.py +54 -10
  128. esphome/dashboard/web_server.py +56 -24
  129. esphome/storage_json.py +4 -0
  130. esphome/wizard.py +13 -17
  131. esphome/writer.py +1 -3
  132. esphome/yaml_util.py +36 -33
  133. esphome/zeroconf.py +9 -21
  134. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/METADATA +5 -5
  135. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/RECORD +139 -99
  136. esphome/components/cst816/binary_sensor/cst816_button.h +0 -27
  137. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/LICENSE +0 -0
  138. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/WHEEL +0 -0
  139. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/entry_points.txt +0 -0
  140. {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,11 @@
8
8
  #include "esphome/core/controller.h"
9
9
  #include "esphome/core/entity_base.h"
10
10
 
11
+ #include <functional>
12
+ #include <list>
11
13
  #include <map>
14
+ #include <string>
15
+ #include <utility>
12
16
  #include <vector>
13
17
  #ifdef USE_ESP32
14
18
  #include <freertos/FreeRTOS.h>
@@ -54,6 +58,85 @@ struct SortingGroup {
54
58
 
55
59
  enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
56
60
 
61
+ /*
62
+ In order to defer updates in arduino mode, we need to create one AsyncEventSource per incoming request to /events.
63
+ This is because only minimal changes were made to the ESPAsyncWebServer lib_dep, it was undesirable to put deferred
64
+ update logic into that library. We need one deferred queue per connection so instead of one AsyncEventSource with
65
+ multiple clients, we have multiple event sources with one client each. This is slightly awkward which is why it's
66
+ implemented in a more straightforward way for ESP-IDF. Arudino platform will eventually go away and this workaround
67
+ can be forgotten.
68
+ */
69
+ #ifdef USE_ARDUINO
70
+ using message_generator_t = std::string(WebServer *, void *);
71
+
72
+ class DeferredUpdateEventSourceList;
73
+ class DeferredUpdateEventSource : public AsyncEventSource {
74
+ friend class DeferredUpdateEventSourceList;
75
+
76
+ /*
77
+ This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function
78
+ that will lazily generate that event. The two pointers allow dedup in the deferred queue if multiple publishes for
79
+ the same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a
80
+ std::vector) is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per
81
+ entry (and no heap fragmentation). Even 100 backed up events (you'd have to have at least 100 sensors publishing
82
+ because of dedup) would take up only 0.8 kB.
83
+ */
84
+ struct DeferredEvent {
85
+ friend class DeferredUpdateEventSource;
86
+
87
+ protected:
88
+ void *source_;
89
+ message_generator_t *message_generator_;
90
+
91
+ public:
92
+ DeferredEvent(void *source, message_generator_t *message_generator)
93
+ : source_(source), message_generator_(message_generator) {}
94
+ bool operator==(const DeferredEvent &test) const {
95
+ return (source_ == test.source_ && message_generator_ == test.message_generator_);
96
+ }
97
+ } __attribute__((packed));
98
+
99
+ protected:
100
+ // surface a couple methods from the base class
101
+ using AsyncEventSource::handleRequest;
102
+ using AsyncEventSource::try_send;
103
+
104
+ ListEntitiesIterator entities_iterator_;
105
+ // vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
106
+ // footprint is more important than speed here)
107
+ std::vector<DeferredEvent> deferred_queue_;
108
+ WebServer *web_server_;
109
+
110
+ // helper for allowing only unique entries in the queue
111
+ void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);
112
+
113
+ void process_deferred_queue_();
114
+
115
+ public:
116
+ DeferredUpdateEventSource(WebServer *ws, const String &url)
117
+ : AsyncEventSource(url), entities_iterator_(ListEntitiesIterator(ws, this)), web_server_(ws) {}
118
+
119
+ void loop();
120
+
121
+ void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
122
+ void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
123
+ };
124
+
125
+ class DeferredUpdateEventSourceList : public std::list<DeferredUpdateEventSource *> {
126
+ protected:
127
+ void on_client_connect_(WebServer *ws, DeferredUpdateEventSource *source);
128
+ void on_client_disconnect_(DeferredUpdateEventSource *source);
129
+
130
+ public:
131
+ void loop();
132
+
133
+ void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
134
+ void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
135
+
136
+ void add_new_client(WebServer *ws, AsyncWebServerRequest *request);
137
+ };
138
+ #endif
139
+
57
140
  /** This class allows users to create a web server with their ESP nodes.
58
141
  *
59
142
  * Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things:
@@ -64,6 +147,10 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
64
147
  * can be found under https://esphome.io/web-api/index.html.
65
148
  */
66
149
  class WebServer : public Controller, public Component, public AsyncWebHandler {
150
+ #ifdef USE_ARDUINO
151
+ friend class DeferredUpdateEventSourceList;
152
+ #endif
153
+
67
154
  public:
68
155
  WebServer(web_server_base::WebServerBase *base);
69
156
 
@@ -153,6 +240,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
153
240
  /// Handle a sensor request under '/sensor/<id>'.
154
241
  void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
155
242
 
243
+ static std::string sensor_state_json_generator(WebServer *web_server, void *source);
244
+ static std::string sensor_all_json_generator(WebServer *web_server, void *source);
156
245
  /// Dump the sensor state with its value as a JSON string.
157
246
  std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config);
158
247
  #endif
@@ -163,6 +252,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
163
252
  /// Handle a switch request under '/switch/<id>/</turn_on/turn_off/toggle>'.
164
253
  void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
165
254
 
255
+ static std::string switch_state_json_generator(WebServer *web_server, void *source);
256
+ static std::string switch_all_json_generator(WebServer *web_server, void *source);
166
257
  /// Dump the switch state with its value as a JSON string.
167
258
  std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config);
168
259
  #endif
@@ -171,6 +262,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
171
262
  /// Handle a button request under '/button/<id>/press'.
172
263
  void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
173
264
 
265
+ static std::string button_state_json_generator(WebServer *web_server, void *source);
266
+ static std::string button_all_json_generator(WebServer *web_server, void *source);
174
267
  /// Dump the button details with its value as a JSON string.
175
268
  std::string button_json(button::Button *obj, JsonDetail start_config);
176
269
  #endif
@@ -181,6 +274,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
181
274
  /// Handle a binary sensor request under '/binary_sensor/<id>'.
182
275
  void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
183
276
 
277
+ static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source);
278
+ static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source);
184
279
  /// Dump the binary sensor state with its value as a JSON string.
185
280
  std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config);
186
281
  #endif
@@ -191,6 +286,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
191
286
  /// Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'.
192
287
  void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
193
288
 
289
+ static std::string fan_state_json_generator(WebServer *web_server, void *source);
290
+ static std::string fan_all_json_generator(WebServer *web_server, void *source);
194
291
  /// Dump the fan state as a JSON string.
195
292
  std::string fan_json(fan::Fan *obj, JsonDetail start_config);
196
293
  #endif
@@ -201,6 +298,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
201
298
  /// Handle a light request under '/light/<id>/</turn_on/turn_off/toggle>'.
202
299
  void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
203
300
 
301
+ static std::string light_state_json_generator(WebServer *web_server, void *source);
302
+ static std::string light_all_json_generator(WebServer *web_server, void *source);
204
303
  /// Dump the light state as a JSON string.
205
304
  std::string light_json(light::LightState *obj, JsonDetail start_config);
206
305
  #endif
@@ -211,6 +310,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
211
310
  /// Handle a text sensor request under '/text_sensor/<id>'.
212
311
  void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
213
312
 
313
+ static std::string text_sensor_state_json_generator(WebServer *web_server, void *source);
314
+ static std::string text_sensor_all_json_generator(WebServer *web_server, void *source);
214
315
  /// Dump the text sensor state with its value as a JSON string.
215
316
  std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config);
216
317
  #endif
@@ -221,6 +322,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
221
322
  /// Handle a cover request under '/cover/<id>/<open/close/stop/set>'.
222
323
  void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
223
324
 
325
+ static std::string cover_state_json_generator(WebServer *web_server, void *source);
326
+ static std::string cover_all_json_generator(WebServer *web_server, void *source);
224
327
  /// Dump the cover state as a JSON string.
225
328
  std::string cover_json(cover::Cover *obj, JsonDetail start_config);
226
329
  #endif
@@ -230,6 +333,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
230
333
  /// Handle a number request under '/number/<id>'.
231
334
  void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
232
335
 
336
+ static std::string number_state_json_generator(WebServer *web_server, void *source);
337
+ static std::string number_all_json_generator(WebServer *web_server, void *source);
233
338
  /// Dump the number state with its value as a JSON string.
234
339
  std::string number_json(number::Number *obj, float value, JsonDetail start_config);
235
340
  #endif
@@ -239,6 +344,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
239
344
  /// Handle a date request under '/date/<id>'.
240
345
  void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match);
241
346
 
347
+ static std::string date_state_json_generator(WebServer *web_server, void *source);
348
+ static std::string date_all_json_generator(WebServer *web_server, void *source);
242
349
  /// Dump the date state with its value as a JSON string.
243
350
  std::string date_json(datetime::DateEntity *obj, JsonDetail start_config);
244
351
  #endif
@@ -248,6 +355,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
248
355
  /// Handle a time request under '/time/<id>'.
249
356
  void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match);
250
357
 
358
+ static std::string time_state_json_generator(WebServer *web_server, void *source);
359
+ static std::string time_all_json_generator(WebServer *web_server, void *source);
251
360
  /// Dump the time state with its value as a JSON string.
252
361
  std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config);
253
362
  #endif
@@ -257,6 +366,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
257
366
  /// Handle a datetime request under '/datetime/<id>'.
258
367
  void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match);
259
368
 
369
+ static std::string datetime_state_json_generator(WebServer *web_server, void *source);
370
+ static std::string datetime_all_json_generator(WebServer *web_server, void *source);
260
371
  /// Dump the datetime state with its value as a JSON string.
261
372
  std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config);
262
373
  #endif
@@ -266,6 +377,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
266
377
  /// Handle a text input request under '/text/<id>'.
267
378
  void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match);
268
379
 
380
+ static std::string text_state_json_generator(WebServer *web_server, void *source);
381
+ static std::string text_all_json_generator(WebServer *web_server, void *source);
269
382
  /// Dump the text state with its value as a JSON string.
270
383
  std::string text_json(text::Text *obj, const std::string &value, JsonDetail start_config);
271
384
  #endif
@@ -275,6 +388,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
275
388
  /// Handle a select request under '/select/<id>'.
276
389
  void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
277
390
 
391
+ static std::string select_state_json_generator(WebServer *web_server, void *source);
392
+ static std::string select_all_json_generator(WebServer *web_server, void *source);
278
393
  /// Dump the select state with its value as a JSON string.
279
394
  std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config);
280
395
  #endif
@@ -284,6 +399,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
284
399
  /// Handle a climate request under '/climate/<id>'.
285
400
  void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
286
401
 
402
+ static std::string climate_state_json_generator(WebServer *web_server, void *source);
403
+ static std::string climate_all_json_generator(WebServer *web_server, void *source);
287
404
  /// Dump the climate details
288
405
  std::string climate_json(climate::Climate *obj, JsonDetail start_config);
289
406
  #endif
@@ -294,6 +411,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
294
411
  /// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
295
412
  void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
296
413
 
414
+ static std::string lock_state_json_generator(WebServer *web_server, void *source);
415
+ static std::string lock_all_json_generator(WebServer *web_server, void *source);
297
416
  /// Dump the lock state with its value as a JSON string.
298
417
  std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
299
418
  #endif
@@ -304,6 +423,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
304
423
  /// Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
305
424
  void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match);
306
425
 
426
+ static std::string valve_state_json_generator(WebServer *web_server, void *source);
427
+ static std::string valve_all_json_generator(WebServer *web_server, void *source);
307
428
  /// Dump the valve state as a JSON string.
308
429
  std::string valve_json(valve::Valve *obj, JsonDetail start_config);
309
430
  #endif
@@ -314,6 +435,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
314
435
  /// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
315
436
  void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
316
437
 
438
+ static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source);
439
+ static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source);
317
440
  /// Dump the alarm_control_panel state with its value as a JSON string.
318
441
  std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
319
442
  alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
@@ -322,6 +445,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
322
445
  #ifdef USE_EVENT
323
446
  void on_event(event::Event *obj, const std::string &event_type) override;
324
447
 
448
+ static std::string event_state_json_generator(WebServer *web_server, void *source);
449
+ static std::string event_all_json_generator(WebServer *web_server, void *source);
450
+
325
451
  /// Handle a event request under '/event<id>'.
326
452
  void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match);
327
453
 
@@ -335,6 +461,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
335
461
  /// Handle a update request under '/update/<id>'.
336
462
  void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match);
337
463
 
464
+ static std::string update_state_json_generator(WebServer *web_server, void *source);
465
+ static std::string update_all_json_generator(WebServer *web_server, void *source);
338
466
  /// Dump the update state with its value as a JSON string.
339
467
  std::string update_json(update::UpdateEntity *obj, JsonDetail start_config);
340
468
  #endif
@@ -349,14 +477,19 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
349
477
  void add_entity_config(EntityBase *entity, float weight, uint64_t group);
350
478
  void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight);
351
479
 
480
+ std::map<EntityBase *, SortingComponents> sorting_entitys_;
481
+ std::map<uint64_t, SortingGroup> sorting_groups_;
482
+ bool include_internal_{false};
483
+
352
484
  protected:
353
485
  void schedule_(std::function<void()> &&f);
354
- friend ListEntitiesIterator;
355
486
  web_server_base::WebServerBase *base_;
356
- AsyncEventSource events_{"/events"};
357
- ListEntitiesIterator entities_iterator_;
358
- std::map<EntityBase *, SortingComponents> sorting_entitys_;
359
- std::map<uint64_t, SortingGroup> sorting_groups_;
487
+ #ifdef USE_ARDUINO
488
+ DeferredUpdateEventSourceList events_;
489
+ #endif
490
+ #ifdef USE_ESP_IDF
491
+ AsyncEventSource events_{"/events", this};
492
+ #endif
360
493
 
361
494
  #if USE_WEBSERVER_VERSION == 1
362
495
  const char *css_url_{nullptr};
@@ -368,7 +501,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
368
501
  #ifdef USE_WEBSERVER_JS_INCLUDE
369
502
  const char *js_include_{nullptr};
370
503
  #endif
371
- bool include_internal_{false};
372
504
  bool allow_ota_{true};
373
505
  bool expose_log_{true};
374
506
  #ifdef USE_ESP32
@@ -37,4 +37,4 @@ async def to_code(config):
37
37
  cg.add_library("FS", None)
38
38
  cg.add_library("Update", None)
39
39
  # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
40
- cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2")
40
+ cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.3.0")
@@ -8,6 +8,8 @@ CONFIG_SCHEMA = cv.All(
8
8
  cv.only_with_esp_idf,
9
9
  )
10
10
 
11
+ AUTO_LOAD = ["web_server"]
12
+
11
13
 
12
14
  async def to_code(config):
13
15
  # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
@@ -8,6 +8,10 @@
8
8
  #include "esp_tls_crypto.h"
9
9
 
10
10
  #include "utils.h"
11
+
12
+ #include "esphome/components/web_server/web_server.h"
13
+ #include "esphome/components/web_server/list_entities.h"
14
+
11
15
  #include "web_server_idf.h"
12
16
 
13
17
  namespace esphome {
@@ -276,21 +280,37 @@ AsyncEventSource::~AsyncEventSource() {
276
280
  }
277
281
 
278
282
  void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
279
- auto *rsp = new AsyncEventSourceResponse(request, this); // NOLINT(cppcoreguidelines-owning-memory)
283
+ auto *rsp = // NOLINT(cppcoreguidelines-owning-memory)
284
+ new AsyncEventSourceResponse(request, this, this->web_server_);
280
285
  if (this->on_connect_) {
281
286
  this->on_connect_(rsp);
282
287
  }
283
288
  this->sessions_.insert(rsp);
284
289
  }
285
290
 
286
- void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
291
+ void AsyncEventSource::loop() {
292
+ for (auto *ses : this->sessions_) {
293
+ ses->loop();
294
+ }
295
+ }
296
+
297
+ void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
298
+ for (auto *ses : this->sessions_) {
299
+ ses->try_send_nodefer(message, event, id, reconnect);
300
+ }
301
+ }
302
+
303
+ void AsyncEventSource::deferrable_send_state(void *source, const char *event_type,
304
+ message_generator_t *message_generator) {
287
305
  for (auto *ses : this->sessions_) {
288
- ses->send(message, event, id, reconnect);
306
+ ses->deferrable_send_state(source, event_type, message_generator);
289
307
  }
290
308
  }
291
309
 
292
- AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server)
293
- : server_(server) {
310
+ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request,
311
+ esphome::web_server_idf::AsyncEventSource *server,
312
+ esphome::web_server::WebServer *ws)
313
+ : server_(server), web_server_(ws), entities_iterator_(new esphome::web_server::ListEntitiesIterator(ws, server)) {
294
314
  httpd_req_t *req = *request;
295
315
 
296
316
  httpd_resp_set_status(req, HTTPD_200);
@@ -309,6 +329,30 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
309
329
 
310
330
  this->hd_ = req->handle;
311
331
  this->fd_ = httpd_req_to_sockfd(req);
332
+
333
+ // Configure reconnect timeout and send config
334
+ // this should always go through since the tcp send buffer is empty on connect
335
+ std::string message = ws->get_config_json();
336
+ this->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
337
+
338
+ for (auto &group : ws->sorting_groups_) {
339
+ message = json::build_json([group](JsonObject root) {
340
+ root["name"] = group.second.name;
341
+ root["sorting_weight"] = group.second.weight;
342
+ });
343
+
344
+ // a (very) large number of these should be able to be queued initially without defer
345
+ // since the only thing in the send buffer at this point is the initial ping/config
346
+ this->try_send_nodefer(message.c_str(), "sorting_group");
347
+ }
348
+
349
+ this->entities_iterator_->begin(ws->include_internal_);
350
+
351
+ // just dump them all up-front and take advantage of the deferred queue
352
+ // on second thought that takes too long, but leaving the commented code here for debug purposes
353
+ // while(!this->entities_iterator_->completed()) {
354
+ // this->entities_iterator_->advance();
355
+ //}
312
356
  }
313
357
 
314
358
  void AsyncEventSourceResponse::destroy(void *ptr) {
@@ -317,52 +361,155 @@ void AsyncEventSourceResponse::destroy(void *ptr) {
317
361
  delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
318
362
  }
319
363
 
320
- void AsyncEventSourceResponse::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
321
- if (this->fd_ == 0) {
364
+ // helper for allowing only unique entries in the queue
365
+ void AsyncEventSourceResponse::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) {
366
+ DeferredEvent item(source, message_generator);
367
+
368
+ auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
369
+ [&item](const DeferredEvent &test) -> bool { return test == item; });
370
+
371
+ if (iter != this->deferred_queue_.end()) {
372
+ (*iter) = item;
373
+ } else {
374
+ this->deferred_queue_.push_back(item);
375
+ }
376
+ }
377
+
378
+ void AsyncEventSourceResponse::process_deferred_queue_() {
379
+ while (!deferred_queue_.empty()) {
380
+ DeferredEvent &de = deferred_queue_.front();
381
+ std::string message = de.message_generator_(web_server_, de.source_);
382
+ if (this->try_send_nodefer(message.c_str(), "state")) {
383
+ // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
384
+ deferred_queue_.erase(deferred_queue_.begin());
385
+ } else {
386
+ break;
387
+ }
388
+ }
389
+ }
390
+
391
+ void AsyncEventSourceResponse::process_buffer_() {
392
+ if (event_buffer_.empty()) {
393
+ return;
394
+ }
395
+ if (event_bytes_sent_ == event_buffer_.size()) {
396
+ event_buffer_.resize(0);
397
+ event_bytes_sent_ = 0;
322
398
  return;
323
399
  }
324
400
 
325
- std::string ev;
401
+ int bytes_sent = httpd_socket_send(this->hd_, this->fd_, event_buffer_.c_str() + event_bytes_sent_,
402
+ event_buffer_.size() - event_bytes_sent_, 0);
403
+ if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT || bytes_sent == HTTPD_SOCK_ERR_FAIL) {
404
+ return;
405
+ }
406
+ event_bytes_sent_ += bytes_sent;
407
+
408
+ if (event_bytes_sent_ == event_buffer_.size()) {
409
+ event_buffer_.resize(0);
410
+ event_bytes_sent_ = 0;
411
+ }
412
+ }
413
+
414
+ void AsyncEventSourceResponse::loop() {
415
+ process_buffer_();
416
+ process_deferred_queue_();
417
+ if (!this->entities_iterator_->completed())
418
+ this->entities_iterator_->advance();
419
+ }
420
+
421
+ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char *event, uint32_t id,
422
+ uint32_t reconnect) {
423
+ if (this->fd_ == 0) {
424
+ return false;
425
+ }
426
+
427
+ process_buffer_();
428
+ if (!event_buffer_.empty()) {
429
+ // there is still pending event data to send first
430
+ return false;
431
+ }
432
+
433
+ // 8 spaces are standing in for the hexidecimal chunk length to print later
434
+ const char chunk_len_header[] = " " CRLF_STR;
435
+ const int chunk_len_header_len = sizeof(chunk_len_header) - 1;
436
+
437
+ event_buffer_.append(chunk_len_header);
326
438
 
327
439
  if (reconnect) {
328
- ev.append("retry: ", sizeof("retry: ") - 1);
329
- ev.append(to_string(reconnect));
330
- ev.append(CRLF_STR, CRLF_LEN);
440
+ event_buffer_.append("retry: ", sizeof("retry: ") - 1);
441
+ event_buffer_.append(to_string(reconnect));
442
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
331
443
  }
332
444
 
333
445
  if (id) {
334
- ev.append("id: ", sizeof("id: ") - 1);
335
- ev.append(to_string(id));
336
- ev.append(CRLF_STR, CRLF_LEN);
446
+ event_buffer_.append("id: ", sizeof("id: ") - 1);
447
+ event_buffer_.append(to_string(id));
448
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
337
449
  }
338
450
 
339
451
  if (event && *event) {
340
- ev.append("event: ", sizeof("event: ") - 1);
341
- ev.append(event);
342
- ev.append(CRLF_STR, CRLF_LEN);
452
+ event_buffer_.append("event: ", sizeof("event: ") - 1);
453
+ event_buffer_.append(event);
454
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
343
455
  }
344
456
 
345
457
  if (message && *message) {
346
- ev.append("data: ", sizeof("data: ") - 1);
347
- ev.append(message);
348
- ev.append(CRLF_STR, CRLF_LEN);
458
+ event_buffer_.append("data: ", sizeof("data: ") - 1);
459
+ event_buffer_.append(message);
460
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
349
461
  }
350
462
 
351
- if (ev.empty()) {
352
- return;
463
+ if (event_buffer_.empty()) {
464
+ return true;
353
465
  }
354
466
 
355
- ev.append(CRLF_STR, CRLF_LEN);
467
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
468
+ event_buffer_.append(CRLF_STR, CRLF_LEN);
469
+
470
+ // chunk length header itself and the final chunk terminating CRLF are not counted as part of the chunk
471
+ int chunk_len = event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
472
+ char chunk_len_str[9];
473
+ snprintf(chunk_len_str, 9, "%08x", chunk_len);
474
+ std::memcpy(&event_buffer_[0], chunk_len_str, 8);
356
475
 
357
- // Sending chunked content prelude
358
- auto cs = str_snprintf("%x" CRLF_STR, 4 * sizeof(ev.size()) + CRLF_LEN, ev.size());
359
- httpd_socket_send(this->hd_, this->fd_, cs.c_str(), cs.size(), 0);
476
+ event_bytes_sent_ = 0;
477
+ process_buffer_();
478
+
479
+ return true;
480
+ }
360
481
 
361
- // Sendiing content chunk
362
- httpd_socket_send(this->hd_, this->fd_, ev.c_str(), ev.size(), 0);
482
+ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *event_type,
483
+ message_generator_t *message_generator) {
484
+ // allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
485
+ // up in the web GUI and reduces event load during initial connect
486
+ if (!entities_iterator_->completed() && 0 != strcmp(event_type, "state_detail_all"))
487
+ return;
363
488
 
364
- // Indicate end of chunk
365
- httpd_socket_send(this->hd_, this->fd_, CRLF_STR, CRLF_LEN, 0);
489
+ if (source == nullptr)
490
+ return;
491
+ if (event_type == nullptr)
492
+ return;
493
+ if (message_generator == nullptr)
494
+ return;
495
+
496
+ if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
497
+ ESP_LOGE(TAG, "Can't defer non-state event");
498
+ }
499
+
500
+ process_buffer_();
501
+ process_deferred_queue_();
502
+
503
+ if (!event_buffer_.empty() || !deferred_queue_.empty()) {
504
+ // outgoing event buffer or deferred queue still not empty which means downstream tcp send buffer full, no point
505
+ // trying to send first
506
+ deq_push_back_with_dedup_(source, message_generator);
507
+ } else {
508
+ std::string message = message_generator(web_server_, source);
509
+ if (!this->try_send_nodefer(message.c_str(), "state")) {
510
+ deq_push_back_with_dedup_(source, message_generator);
511
+ }
512
+ }
366
513
  }
367
514
 
368
515
  } // namespace web_server_idf