esphome 2025.6.2__py3-none-any.whl → 2025.7.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 (648) hide show
  1. esphome/__main__.py +1 -3
  2. esphome/codegen.py +2 -0
  3. esphome/components/ac_dimmer/ac_dimmer.cpp +6 -6
  4. esphome/components/adc/__init__.py +25 -1
  5. esphome/components/adc/adc_sensor.h +11 -11
  6. esphome/components/adc/adc_sensor_common.cpp +1 -1
  7. esphome/components/adc/adc_sensor_esp32.cpp +16 -8
  8. esphome/components/ade7880/ade7880.h +0 -2
  9. esphome/components/ads1115/ads1115.h +0 -1
  10. esphome/components/ads1118/ads1118.h +0 -1
  11. esphome/components/ags10/ags10.h +0 -2
  12. esphome/components/aic3204/aic3204.h +0 -1
  13. esphome/components/alarm_control_panel/__init__.py +5 -2
  14. esphome/components/alpha3/alpha3.h +0 -1
  15. esphome/components/am43/cover/am43_cover.h +0 -1
  16. esphome/components/am43/sensor/am43_sensor.h +0 -1
  17. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +0 -2
  18. esphome/components/anova/anova.cpp +5 -1
  19. esphome/components/anova/anova.h +0 -1
  20. esphome/components/apds9960/apds9960.cpp +1 -1
  21. esphome/components/api/__init__.py +57 -21
  22. esphome/components/api/api_connection.cpp +344 -539
  23. esphome/components/api/api_connection.h +224 -141
  24. esphome/components/api/api_frame_helper.cpp +91 -127
  25. esphome/components/api/api_frame_helper.h +64 -54
  26. esphome/components/api/api_pb2.cpp +1837 -9044
  27. esphome/components/api/api_pb2.h +532 -685
  28. esphome/components/api/api_pb2_dump.cpp +4432 -0
  29. esphome/components/api/api_pb2_service.cpp +184 -425
  30. esphome/components/api/api_pb2_service.h +13 -6
  31. esphome/components/api/api_server.cpp +131 -167
  32. esphome/components/api/api_server.h +38 -10
  33. esphome/components/api/client.py +10 -4
  34. esphome/components/api/custom_api_device.h +8 -0
  35. esphome/components/api/list_entities.cpp +37 -104
  36. esphome/components/api/list_entities.h +33 -23
  37. esphome/components/api/proto.h +532 -26
  38. esphome/components/api/subscribe_state.cpp +23 -29
  39. esphome/components/api/subscribe_state.h +26 -19
  40. esphome/components/api/user_services.h +2 -0
  41. esphome/components/as3935_spi/as3935_spi.h +0 -2
  42. esphome/components/as5600/as5600.h +0 -1
  43. esphome/components/async_tcp/__init__.py +14 -5
  44. esphome/components/atc_mithermometer/atc_mithermometer.h +0 -1
  45. esphome/components/atm90e32/atm90e32.cpp +2 -1
  46. esphome/components/audio/audio_decoder.cpp +1 -1
  47. esphome/components/audio/audio_transfer_buffer.cpp +2 -2
  48. esphome/components/b_parasite/b_parasite.h +0 -1
  49. esphome/components/bedjet/bedjet_hub.cpp +5 -1
  50. esphome/components/bedjet/climate/bedjet_climate.cpp +5 -1
  51. esphome/components/beken_spi_led_strip/led_strip.cpp +4 -2
  52. esphome/components/bh1750/bh1750.cpp +5 -5
  53. esphome/components/binary_sensor/__init__.py +82 -5
  54. esphome/components/binary_sensor/automation.h +19 -1
  55. esphome/components/binary_sensor/binary_sensor.cpp +12 -30
  56. esphome/components/binary_sensor/binary_sensor.h +11 -25
  57. esphome/components/binary_sensor/filter.cpp +29 -24
  58. esphome/components/binary_sensor/filter.h +20 -10
  59. esphome/components/ble_client/output/ble_binary_output.h +0 -1
  60. esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +5 -1
  61. esphome/components/ble_client/sensor/ble_rssi_sensor.h +0 -1
  62. esphome/components/ble_client/sensor/ble_sensor.cpp +5 -1
  63. esphome/components/ble_client/sensor/ble_sensor.h +0 -1
  64. esphome/components/ble_client/switch/ble_switch.h +0 -1
  65. esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +5 -1
  66. esphome/components/ble_client/text_sensor/ble_text_sensor.h +0 -1
  67. esphome/components/ble_presence/ble_presence_device.h +0 -1
  68. esphome/components/ble_rssi/ble_rssi_sensor.h +0 -1
  69. esphome/components/ble_scanner/ble_scanner.h +0 -1
  70. esphome/components/bluetooth_proxy/bluetooth_connection.h +9 -2
  71. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +16 -6
  72. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -2
  73. esphome/components/bme680/sensor.py +1 -1
  74. esphome/components/bmp581/bmp581.h +0 -2
  75. esphome/components/button/__init__.py +5 -2
  76. esphome/components/camera/__init__.py +1 -0
  77. esphome/components/camera/camera.cpp +22 -0
  78. esphome/components/camera/camera.h +80 -0
  79. esphome/components/canbus/__init__.py +1 -0
  80. esphome/components/cap1188/cap1188.h +0 -1
  81. esphome/components/captive_portal/__init__.py +12 -2
  82. esphome/components/captive_portal/captive_portal.cpp +12 -2
  83. esphome/components/captive_portal/captive_portal.h +5 -2
  84. esphome/components/ccs811/ccs811.h +0 -2
  85. esphome/components/climate/__init__.py +5 -2
  86. esphome/components/cm1106/sensor.py +2 -2
  87. esphome/components/const/__init__.py +2 -0
  88. esphome/components/copy/binary_sensor/copy_binary_sensor.h +0 -1
  89. esphome/components/copy/button/copy_button.h +0 -1
  90. esphome/components/copy/cover/copy_cover.h +0 -1
  91. esphome/components/copy/fan/copy_fan.h +0 -1
  92. esphome/components/copy/lock/copy_lock.h +0 -1
  93. esphome/components/copy/number/copy_number.h +0 -1
  94. esphome/components/copy/select/copy_select.h +0 -1
  95. esphome/components/copy/sensor/copy_sensor.h +0 -1
  96. esphome/components/copy/switch/copy_switch.h +0 -1
  97. esphome/components/copy/text/copy_text.h +0 -1
  98. esphome/components/copy/text_sensor/copy_text_sensor.h +0 -1
  99. esphome/components/cover/__init__.py +5 -2
  100. esphome/components/cs5460a/cs5460a.h +0 -1
  101. esphome/components/datetime/__init__.py +4 -2
  102. esphome/components/debug/__init__.py +20 -0
  103. esphome/components/debug/debug_esp32.cpp +2 -0
  104. esphome/components/deep_sleep/__init__.py +43 -9
  105. esphome/components/demo/__init__.py +2 -2
  106. esphome/components/display/display.cpp +4 -3
  107. esphome/components/display/display.h +0 -2
  108. esphome/components/display/display_buffer.cpp +1 -1
  109. esphome/components/ds2484/__init__.py +1 -0
  110. esphome/components/ds2484/ds2484.cpp +209 -0
  111. esphome/components/ds2484/ds2484.h +43 -0
  112. esphome/components/ds2484/one_wire.py +37 -0
  113. esphome/components/duty_time/duty_time_sensor.h +0 -1
  114. esphome/components/ens160_base/ens160_base.h +0 -1
  115. esphome/components/es7210/es7210.h +0 -1
  116. esphome/components/es7243e/es7243e.h +0 -1
  117. esphome/components/es8156/es8156.h +0 -1
  118. esphome/components/es8311/es8311.h +0 -1
  119. esphome/components/es8388/es8388.h +0 -1
  120. esphome/components/esp32/__init__.py +103 -135
  121. esphome/components/esp32/core.cpp +0 -4
  122. esphome/components/esp32/gpio.h +1 -1
  123. esphome/components/esp32/helpers.cpp +69 -0
  124. esphome/components/esp32_ble/ble.cpp +5 -6
  125. esphome/components/esp32_ble/ble.h +29 -14
  126. esphome/components/esp32_ble/ble_event.h +6 -6
  127. esphome/components/esp32_ble_client/ble_client_base.cpp +21 -6
  128. esphome/components/esp32_ble_client/ble_client_base.h +24 -9
  129. esphome/components/esp32_ble_tracker/__init__.py +2 -8
  130. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +5 -5
  131. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +11 -7
  132. esphome/components/esp32_camera/__init__.py +112 -98
  133. esphome/components/esp32_camera/esp32_camera.cpp +41 -31
  134. esphome/components/esp32_camera/esp32_camera.h +35 -30
  135. esphome/components/esp32_camera_web_server/__init__.py +2 -1
  136. esphome/components/esp32_camera_web_server/camera_web_server.cpp +8 -8
  137. esphome/components/esp32_camera_web_server/camera_web_server.h +3 -3
  138. esphome/components/esp32_hall/sensor.py +2 -21
  139. esphome/components/esp32_hosted/__init__.py +101 -0
  140. esphome/components/esp32_hosted/esp32_hosted.py.script +12 -0
  141. esphome/components/esp32_improv/esp32_improv_component.cpp +3 -0
  142. esphome/components/esp32_rmt/__init__.py +0 -58
  143. esphome/components/esp32_rmt_led_strip/led_strip.cpp +77 -63
  144. esphome/components/esp32_rmt_led_strip/led_strip.h +11 -17
  145. esphome/components/esp32_rmt_led_strip/light.py +14 -76
  146. esphome/components/esp32_touch/esp32_touch.h +174 -28
  147. esphome/components/esp32_touch/esp32_touch_common.cpp +162 -0
  148. esphome/components/esp32_touch/esp32_touch_v1.cpp +240 -0
  149. esphome/components/esp32_touch/esp32_touch_v2.cpp +397 -0
  150. esphome/components/esp8266/__init__.py +2 -0
  151. esphome/components/esp8266/gpio.cpp +10 -10
  152. esphome/components/esp8266/helpers.cpp +31 -0
  153. esphome/components/esp_ldo/__init__.py +10 -8
  154. esphome/components/esp_ldo/esp_ldo.h +3 -0
  155. esphome/components/esphome/ota/__init__.py +1 -0
  156. esphome/components/esphome/ota/ota_esphome.cpp +24 -19
  157. esphome/components/ethernet/__init__.py +42 -23
  158. esphome/components/ethernet/esp_eth_phy_jl1101.c +0 -16
  159. esphome/components/ethernet/ethernet_component.cpp +69 -29
  160. esphome/components/ethernet/ethernet_component.h +18 -10
  161. esphome/components/event/__init__.py +5 -2
  162. esphome/components/ezo/ezo.h +0 -1
  163. esphome/components/ezo_pmp/ezo_pmp.h +0 -1
  164. esphome/components/fan/__init__.py +5 -2
  165. esphome/components/fan/fan.cpp +4 -0
  166. esphome/components/feedback/feedback_cover.h +0 -1
  167. esphome/components/font/__init__.py +92 -82
  168. esphome/components/font/font.cpp +9 -2
  169. esphome/components/font/font.h +20 -5
  170. esphome/components/fs3000/fs3000.h +0 -1
  171. esphome/components/gcja5/gcja5.h +0 -1
  172. esphome/components/gl_r01_i2c/__init__.py +0 -0
  173. esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +68 -0
  174. esphome/components/gl_r01_i2c/gl_r01_i2c.h +22 -0
  175. esphome/components/gl_r01_i2c/sensor.py +36 -0
  176. esphome/components/gp8403/gp8403.h +0 -1
  177. esphome/components/gpio/binary_sensor/__init__.py +39 -1
  178. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +77 -3
  179. esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +40 -0
  180. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h +0 -2
  181. esphome/components/he60r/he60r.h +0 -1
  182. esphome/components/heatpumpir/climate.py +2 -1
  183. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  184. esphome/components/heatpumpir/heatpumpir.h +1 -0
  185. esphome/components/honeywellabp2_i2c/honeywellabp2.h +0 -1
  186. esphome/components/host/__init__.py +3 -1
  187. esphome/components/host/helpers.cpp +57 -0
  188. esphome/components/http_request/__init__.py +19 -1
  189. esphome/components/http_request/http_request.h +1 -1
  190. esphome/components/http_request/http_request_arduino.cpp +0 -1
  191. esphome/components/http_request/http_request_arduino.h +1 -0
  192. esphome/components/http_request/http_request_idf.cpp +0 -1
  193. esphome/components/http_request/ota/ota_http_request.cpp +1 -1
  194. esphome/components/http_request/update/http_request_update.cpp +35 -16
  195. esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +3 -9
  196. esphome/components/hydreon_rgxx/sensor.py +1 -1
  197. esphome/components/i2c/__init__.py +23 -11
  198. esphome/components/i2c/i2c_bus.h +8 -1
  199. esphome/components/i2c/i2c_bus_arduino.cpp +4 -3
  200. esphome/components/i2c/i2c_bus_arduino.h +6 -3
  201. esphome/components/i2c/i2c_bus_esp_idf.h +5 -3
  202. esphome/components/i2c_device/i2c_device.h +0 -1
  203. esphome/components/i2s_audio/__init__.py +2 -10
  204. esphome/components/i2s_audio/i2s_audio.cpp +1 -5
  205. esphome/components/i2s_audio/media_player/__init__.py +2 -2
  206. esphome/components/i2s_audio/speaker/__init__.py +1 -1
  207. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +2 -2
  208. esphome/components/iaqcore/iaqcore.h +0 -2
  209. esphome/components/image/__init__.py +123 -24
  210. esphome/components/improv_serial/improv_serial_component.cpp +0 -4
  211. esphome/components/ina219/ina219.cpp +7 -0
  212. esphome/components/ina219/ina219.h +1 -0
  213. esphome/components/ina260/ina260.h +0 -2
  214. esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +0 -1
  215. esphome/components/inkplate6/display.py +15 -0
  216. esphome/components/inkplate6/inkplate.cpp +2 -2
  217. esphome/components/integration/integration_sensor.h +0 -1
  218. esphome/components/internal_temperature/internal_temperature.cpp +8 -27
  219. esphome/components/internal_temperature/sensor.py +0 -26
  220. esphome/components/interval/interval.h +0 -2
  221. esphome/components/json/__init__.py +1 -1
  222. esphome/components/json/json_util.cpp +56 -63
  223. esphome/components/ld2410/button/__init__.py +3 -3
  224. esphome/components/ld2410/button/factory_reset_button.cpp +9 -0
  225. esphome/components/ld2410/button/{reset_button.h → factory_reset_button.h} +2 -2
  226. esphome/components/ld2410/ld2410.cpp +421 -268
  227. esphome/components/ld2410/ld2410.h +44 -146
  228. esphome/components/ld2410/number/__init__.py +2 -2
  229. esphome/components/ld2410/sensor.py +1 -1
  230. esphome/components/ld2410/switch/__init__.py +1 -1
  231. esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp +2 -2
  232. esphome/components/ld2420/button/reconfig_buttons.cpp +1 -1
  233. esphome/components/ld2420/ld2420.cpp +252 -147
  234. esphome/components/ld2420/ld2420.h +52 -126
  235. esphome/components/ld2420/number/__init__.py +2 -2
  236. esphome/components/ld2420/number/gate_config_number.cpp +1 -1
  237. esphome/components/ld2420/select/operating_mode_select.cpp +1 -1
  238. esphome/components/ld2420/sensor/__init__.py +6 -2
  239. esphome/components/ld2420/sensor/ld2420_sensor.cpp +2 -2
  240. esphome/components/ld2420/sensor/ld2420_sensor.h +1 -1
  241. esphome/components/ld2420/text_sensor/text_sensor.cpp +2 -2
  242. esphome/components/ld2450/button/__init__.py +3 -3
  243. esphome/components/ld2450/button/factory_reset_button.cpp +9 -0
  244. esphome/components/ld2450/button/{reset_button.h → factory_reset_button.h} +2 -2
  245. esphome/components/ld2450/ld2450.cpp +384 -232
  246. esphome/components/ld2450/ld2450.h +60 -69
  247. esphome/components/ld2450/switch/__init__.py +1 -1
  248. esphome/components/ledc/ledc_output.cpp +1 -63
  249. esphome/components/libretiny/__init__.py +5 -3
  250. esphome/components/libretiny/const.py +5 -0
  251. esphome/components/libretiny/generate_components.py +1 -0
  252. esphome/components/libretiny/helpers.cpp +35 -0
  253. esphome/components/libretiny/lt_component.cpp +5 -3
  254. esphome/components/light/__init__.py +4 -2
  255. esphome/components/light/addressable_light.h +3 -3
  256. esphome/components/light/light_call.cpp +180 -243
  257. esphome/components/light/light_call.h +72 -20
  258. esphome/components/light/light_color_values.h +14 -14
  259. esphome/components/light/light_json_schema.cpp +17 -16
  260. esphome/components/light/light_state.h +15 -13
  261. esphome/components/light/transformers.h +2 -2
  262. esphome/components/ln882x/__init__.py +52 -0
  263. esphome/components/ln882x/boards.py +285 -0
  264. esphome/components/lock/__init__.py +5 -2
  265. esphome/components/logger/__init__.py +40 -3
  266. esphome/components/logger/logger.cpp +47 -12
  267. esphome/components/logger/logger.h +80 -49
  268. esphome/components/logger/logger_esp32.cpp +3 -3
  269. esphome/components/lps22/__init__.py +0 -0
  270. esphome/components/lps22/lps22.cpp +75 -0
  271. esphome/components/lps22/lps22.h +27 -0
  272. esphome/components/lps22/sensor.py +58 -0
  273. esphome/components/ltr390/ltr390.h +0 -1
  274. esphome/components/ltr501/ltr501.h +0 -1
  275. esphome/components/ltr_als_ps/ltr_als_ps.h +0 -1
  276. esphome/components/lvgl/__init__.py +1 -1
  277. esphome/components/lvgl/schemas.py +66 -6
  278. esphome/components/lvgl/styles.py +24 -16
  279. esphome/components/lvgl/widgets/__init__.py +12 -2
  280. esphome/components/lvgl/widgets/lv_bar.py +40 -19
  281. esphome/components/lvgl/widgets/meter.py +20 -13
  282. esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp +1 -1
  283. esphome/components/max9611/max9611.h +0 -1
  284. esphome/components/mcp23016/__init__.py +1 -1
  285. esphome/components/mcp23xxx_base/__init__.py +1 -1
  286. esphome/components/mcp4461/__init__.py +1 -1
  287. esphome/components/mcp4461/output/__init__.py +3 -2
  288. esphome/components/mcp9600/mcp9600.h +0 -2
  289. esphome/components/md5/md5.cpp +3 -3
  290. esphome/components/md5/md5.h +1 -6
  291. esphome/components/mdns/__init__.py +22 -11
  292. esphome/components/media_player/__init__.py +4 -3
  293. esphome/components/micro_wake_word/__init__.py +1 -5
  294. esphome/components/micro_wake_word/streaming_model.cpp +2 -2
  295. esphome/components/microphone/microphone.cpp +7 -9
  296. esphome/components/microphone/microphone.h +0 -2
  297. esphome/components/mipi_spi/display.py +1 -0
  298. esphome/components/mmc5603/mmc5603.cpp +1 -1
  299. esphome/components/modbus/modbus.cpp +33 -15
  300. esphome/components/modbus/modbus.h +9 -0
  301. esphome/components/modbus_controller/__init__.py +42 -10
  302. esphome/components/modbus_controller/modbus_controller.cpp +92 -11
  303. esphome/components/modbus_controller/modbus_controller.h +61 -7
  304. esphome/components/mopeka_pro_check/mopeka_pro_check.h +0 -1
  305. esphome/components/mopeka_std_check/mopeka_std_check.h +0 -1
  306. esphome/components/mpl3115a2/mpl3115a2.h +0 -2
  307. esphome/components/mqtt/__init__.py +16 -0
  308. esphome/components/mqtt/mqtt_alarm_control_panel.cpp +2 -1
  309. esphome/components/mqtt/mqtt_backend.h +2 -1
  310. esphome/components/mqtt/mqtt_backend_esp32.cpp +132 -47
  311. esphome/components/mqtt/mqtt_backend_esp32.h +106 -4
  312. esphome/components/mqtt/mqtt_binary_sensor.cpp +1 -0
  313. esphome/components/mqtt/mqtt_button.cpp +4 -1
  314. esphome/components/mqtt/mqtt_client.cpp +17 -9
  315. esphome/components/mqtt/mqtt_client.h +8 -3
  316. esphome/components/mqtt/mqtt_climate.cpp +6 -4
  317. esphome/components/mqtt/mqtt_component.cpp +3 -1
  318. esphome/components/mqtt/mqtt_cover.cpp +1 -0
  319. esphome/components/mqtt/mqtt_date.cpp +4 -3
  320. esphome/components/mqtt/mqtt_datetime.cpp +7 -6
  321. esphome/components/mqtt/mqtt_event.cpp +6 -3
  322. esphome/components/mqtt/mqtt_fan.cpp +1 -0
  323. esphome/components/mqtt/mqtt_light.cpp +8 -4
  324. esphome/components/mqtt/mqtt_lock.cpp +3 -1
  325. esphome/components/mqtt/mqtt_number.cpp +1 -0
  326. esphome/components/mqtt/mqtt_select.cpp +2 -1
  327. esphome/components/mqtt/mqtt_sensor.cpp +3 -1
  328. esphome/components/mqtt/mqtt_switch.cpp +3 -1
  329. esphome/components/mqtt/mqtt_text.cpp +1 -0
  330. esphome/components/mqtt/mqtt_text_sensor.cpp +3 -1
  331. esphome/components/mqtt/mqtt_time.cpp +4 -3
  332. esphome/components/mqtt/mqtt_update.cpp +1 -0
  333. esphome/components/mqtt/mqtt_valve.cpp +3 -1
  334. esphome/components/ms8607/ms8607.cpp +1 -1
  335. esphome/components/ms8607/ms8607.h +0 -1
  336. esphome/components/neopixelbus/light.py +4 -1
  337. esphome/components/neopixelbus/neopixelbus_light.h +1 -1
  338. esphome/components/network/__init__.py +4 -1
  339. esphome/components/network/ip_address.h +1 -0
  340. esphome/components/nextion/__init__.py +16 -0
  341. esphome/components/nextion/base_component.py +1 -0
  342. esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +1 -1
  343. esphome/components/nextion/display.py +14 -4
  344. esphome/components/nextion/nextion.cpp +166 -101
  345. esphome/components/nextion/nextion.h +84 -53
  346. esphome/components/nextion/nextion_commands.cpp +11 -10
  347. esphome/components/nextion/nextion_component.cpp +28 -28
  348. esphome/components/nextion/nextion_component.h +53 -18
  349. esphome/components/nextion/nextion_component_base.h +3 -0
  350. esphome/components/nextion/nextion_upload.cpp +36 -0
  351. esphome/components/nextion/nextion_upload_arduino.cpp +10 -35
  352. esphome/components/nextion/nextion_upload_idf.cpp +9 -33
  353. esphome/components/nextion/sensor/nextion_sensor.cpp +1 -1
  354. esphome/components/nextion/switch/nextion_switch.cpp +1 -1
  355. esphome/components/nextion/text_sensor/nextion_textsensor.cpp +1 -1
  356. esphome/components/nfc/nfc.cpp +3 -22
  357. esphome/components/nfc/nfc.h +3 -3
  358. esphome/components/number/__init__.py +5 -2
  359. esphome/components/online_image/__init__.py +9 -1
  360. esphome/components/online_image/online_image.cpp +17 -7
  361. esphome/components/online_image/online_image.h +10 -2
  362. esphome/components/opentherm/opentherm.cpp +7 -12
  363. esphome/components/opentherm/output/output.cpp +1 -1
  364. esphome/components/openthread/__init__.py +47 -40
  365. esphome/components/openthread/const.py +1 -0
  366. esphome/components/openthread/openthread_esp.cpp +27 -5
  367. esphome/components/opt3001/__init__.py +0 -0
  368. esphome/components/opt3001/opt3001.cpp +122 -0
  369. esphome/components/opt3001/opt3001.h +27 -0
  370. esphome/components/opt3001/sensor.py +35 -0
  371. esphome/components/ota/__init__.py +17 -0
  372. esphome/components/ota/ota_backend.h +27 -1
  373. esphome/components/ota/ota_backend_arduino_esp32.cpp +12 -2
  374. esphome/components/ota/ota_backend_arduino_esp32.h +3 -0
  375. esphome/components/ota/ota_backend_arduino_esp8266.cpp +18 -4
  376. esphome/components/ota/ota_backend_arduino_esp8266.h +3 -0
  377. esphome/components/ota/ota_backend_arduino_libretiny.cpp +12 -2
  378. esphome/components/ota/ota_backend_arduino_libretiny.h +3 -0
  379. esphome/components/ota/ota_backend_arduino_rp2040.cpp +9 -2
  380. esphome/components/ota/ota_backend_arduino_rp2040.h +3 -0
  381. esphome/components/ota/ota_backend_esp_idf.cpp +10 -16
  382. esphome/components/ota/ota_backend_esp_idf.h +1 -0
  383. esphome/components/packages/__init__.py +5 -2
  384. esphome/components/packet_transport/binary_sensor.py +61 -4
  385. esphome/components/packet_transport/packet_transport.cpp +34 -1
  386. esphome/components/packet_transport/packet_transport.h +11 -5
  387. esphome/components/pcf8574/__init__.py +1 -1
  388. esphome/components/pi4ioe5v6408/__init__.py +84 -0
  389. esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +171 -0
  390. esphome/components/pi4ioe5v6408/pi4ioe5v6408.h +70 -0
  391. esphome/components/pmsa003i/pmsa003i.h +0 -1
  392. esphome/components/pmsx003/pmsx003.h +0 -1
  393. esphome/components/pn7150/pn7150.cpp +7 -7
  394. esphome/components/pn7150/pn7150.h +0 -1
  395. esphome/components/pn7160/pn7160.cpp +7 -7
  396. esphome/components/pn7160/pn7160.h +0 -1
  397. esphome/components/preferences/syncer.h +2 -0
  398. esphome/components/prometheus/prometheus_handler.h +1 -1
  399. esphome/components/psram/psram.cpp +0 -20
  400. esphome/components/pulse_counter/pulse_counter_sensor.h +0 -1
  401. esphome/components/pulse_meter/pulse_meter_sensor.cpp +8 -4
  402. esphome/components/pulse_width/pulse_width.h +0 -1
  403. esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +0 -4
  404. esphome/components/pvvx_mithermometer/display/pvvx_display.h +0 -2
  405. esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +0 -1
  406. esphome/components/qr_code/__init__.py +13 -10
  407. esphome/components/qwiic_pir/qwiic_pir.h +0 -1
  408. esphome/components/radon_eye_ble/radon_eye_listener.cpp +1 -1
  409. esphome/components/rc522/rc522.h +0 -1
  410. esphome/components/rdm6300/rdm6300.h +0 -2
  411. esphome/components/remote_base/__init__.py +7 -5
  412. esphome/components/remote_base/remote_base.cpp +24 -21
  413. esphome/components/remote_base/remote_base.h +3 -26
  414. esphome/components/remote_receiver/__init__.py +40 -46
  415. esphome/components/remote_receiver/remote_receiver.h +4 -18
  416. esphome/components/remote_receiver/remote_receiver_esp32.cpp +0 -87
  417. esphome/components/remote_receiver/remote_receiver_esp8266.cpp +1 -1
  418. esphome/components/remote_transmitter/__init__.py +42 -43
  419. esphome/components/remote_transmitter/remote_transmitter.h +2 -14
  420. esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +0 -77
  421. esphome/components/resistance/resistance_sensor.h +0 -1
  422. esphome/components/rp2040/__init__.py +2 -0
  423. esphome/components/rp2040/helpers.cpp +55 -0
  424. esphome/components/rp2040_pio_led_strip/led_strip.cpp +2 -2
  425. esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +0 -4
  426. esphome/components/rtttl/__init__.py +4 -4
  427. esphome/components/rtttl/rtttl.cpp +10 -1
  428. esphome/components/ruuvitag/ruuvitag.h +0 -1
  429. esphome/components/safe_mode/safe_mode.cpp +2 -0
  430. esphome/components/safe_mode/safe_mode.h +4 -1
  431. esphome/components/scd30/scd30.h +0 -1
  432. esphome/components/scd30/sensor.py +2 -2
  433. esphome/components/scd4x/scd4x.cpp +61 -54
  434. esphome/components/scd4x/scd4x.h +17 -15
  435. esphome/components/scd4x/sensor.py +4 -4
  436. esphome/components/script/script.h +0 -2
  437. esphome/components/sdp3x/sensor.py +1 -1
  438. esphome/components/select/__init__.py +5 -2
  439. esphome/components/sen5x/sen5x.h +0 -1
  440. esphome/components/senseair/senseair.h +0 -1
  441. esphome/components/sensor/__init__.py +4 -2
  442. esphome/components/sensor/filter.cpp +1 -1
  443. esphome/components/sensor/sensor.cpp +12 -6
  444. esphome/components/sensor/sensor.h +13 -5
  445. esphome/components/servo/servo.cpp +2 -2
  446. esphome/components/servo/servo.h +0 -1
  447. esphome/components/sfa30/sfa30.h +0 -1
  448. esphome/components/sgp30/sgp30.h +0 -1
  449. esphome/components/sgp4x/sgp4x.h +0 -1
  450. esphome/components/shelly_dimmer/stm32flash.cpp +1 -2
  451. esphome/components/sht4x/sht4x.h +0 -1
  452. esphome/components/sm300d2/sm300d2.h +0 -2
  453. esphome/components/smt100/sensor.py +8 -4
  454. esphome/components/smt100/smt100.cpp +5 -5
  455. esphome/components/smt100/smt100.h +3 -3
  456. esphome/components/sn74hc595/__init__.py +1 -1
  457. esphome/components/sn74hc595/sn74hc595.cpp +5 -4
  458. esphome/components/sntp/sntp_component.cpp +9 -3
  459. esphome/components/sntp/time.py +2 -0
  460. esphome/components/socket/__init__.py +17 -0
  461. esphome/components/spi/__init__.py +27 -6
  462. esphome/components/spi/spi.cpp +3 -2
  463. esphome/components/spi/spi.h +9 -3
  464. esphome/components/spi/spi_arduino.cpp +3 -5
  465. esphome/components/spi/spi_esp_idf.cpp +40 -21
  466. esphome/components/spi_led_strip/spi_led_strip.cpp +1 -1
  467. esphome/components/sps30/sps30.h +0 -1
  468. esphome/components/ssd1306_base/ssd1306_base.cpp +1 -1
  469. esphome/components/st7701s/st7701s.cpp +0 -4
  470. esphome/components/status/status_binary_sensor.h +0 -2
  471. esphome/components/substitutions/__init__.py +81 -21
  472. esphome/components/substitutions/jinja.py +99 -0
  473. esphome/components/sun/sun.cpp +3 -4
  474. esphome/components/switch/__init__.py +5 -2
  475. esphome/components/switch/binary_sensor/switch_binary_sensor.h +0 -1
  476. esphome/components/sx126x/__init__.py +317 -0
  477. esphome/components/sx126x/automation.h +62 -0
  478. esphome/components/sx126x/packet_transport/__init__.py +26 -0
  479. esphome/components/sx126x/packet_transport/sx126x_transport.cpp +26 -0
  480. esphome/components/sx126x/packet_transport/sx126x_transport.h +25 -0
  481. esphome/components/sx126x/sx126x.cpp +523 -0
  482. esphome/components/sx126x/sx126x.h +140 -0
  483. esphome/components/sx126x/sx126x_reg.h +163 -0
  484. esphome/components/sx127x/__init__.py +325 -0
  485. esphome/components/sx127x/automation.h +62 -0
  486. esphome/components/sx127x/packet_transport/__init__.py +26 -0
  487. esphome/components/sx127x/packet_transport/sx127x_transport.cpp +26 -0
  488. esphome/components/sx127x/packet_transport/sx127x_transport.h +25 -0
  489. esphome/components/sx127x/sx127x.cpp +498 -0
  490. esphome/components/sx127x/sx127x.h +128 -0
  491. esphome/components/sx127x/sx127x_reg.h +295 -0
  492. esphome/components/syslog/esphome_syslog.cpp +5 -3
  493. esphome/components/syslog/esphome_syslog.h +1 -1
  494. esphome/components/tca9555/__init__.py +1 -1
  495. esphome/components/template/binary_sensor/template_binary_sensor.cpp +1 -9
  496. esphome/components/text/__init__.py +5 -2
  497. esphome/components/text_sensor/__init__.py +5 -2
  498. esphome/components/thermostat/thermostat_climate.cpp +34 -31
  499. esphome/components/thermostat/thermostat_climate.h +43 -39
  500. esphome/components/time/__init__.py +16 -2
  501. esphome/components/time/real_time_clock.cpp +4 -0
  502. esphome/components/time/real_time_clock.h +5 -1
  503. esphome/components/tlc5971/tlc5971.cpp +4 -1
  504. esphome/components/tmp1075/tmp1075.h +0 -2
  505. esphome/components/tof10120/tof10120_sensor.h +0 -1
  506. esphome/components/tormatic/tormatic_cover.h +0 -1
  507. esphome/components/total_daily_energy/total_daily_energy.h +0 -1
  508. esphome/components/tsl2591/tsl2591.cpp +1 -1
  509. esphome/components/ttp229_bsf/ttp229_bsf.h +0 -1
  510. esphome/components/ttp229_lsf/ttp229_lsf.h +0 -1
  511. esphome/components/tx20/tx20.cpp +2 -2
  512. esphome/components/uart/__init__.py +18 -0
  513. esphome/components/uart/uart_component_esp_idf.cpp +1 -5
  514. esphome/components/update/__init__.py +5 -2
  515. esphome/components/update/update_entity.h +8 -0
  516. esphome/components/usb_host/__init__.py +5 -2
  517. esphome/components/usb_host/usb_host_client.cpp +10 -10
  518. esphome/components/usb_uart/cp210x.cpp +1 -1
  519. esphome/components/usb_uart/usb_uart.cpp +41 -44
  520. esphome/components/usb_uart/usb_uart.h +4 -3
  521. esphome/components/valve/__init__.py +5 -2
  522. esphome/components/vbus/vbus.h +0 -1
  523. esphome/components/veml3235/veml3235.h +0 -1
  524. esphome/components/veml7700/veml7700.h +0 -1
  525. esphome/components/vl53l0x/vl53l0x_sensor.h +0 -1
  526. esphome/components/voice_assistant/voice_assistant.cpp +4 -4
  527. esphome/components/watchdog/watchdog.cpp +0 -4
  528. esphome/components/waveshare_epaper/waveshare_epaper.cpp +6 -6
  529. esphome/components/web_server/__init__.py +34 -19
  530. esphome/components/web_server/ota/__init__.py +32 -0
  531. esphome/components/web_server/ota/ota_web_server.cpp +210 -0
  532. esphome/components/web_server/ota/ota_web_server.h +26 -0
  533. esphome/components/web_server/web_server.cpp +324 -439
  534. esphome/components/web_server/web_server.h +33 -23
  535. esphome/components/web_server/web_server_v1.cpp +4 -5
  536. esphome/components/web_server_base/__init__.py +5 -2
  537. esphome/components/web_server_base/web_server_base.cpp +2 -94
  538. esphome/components/web_server_base/web_server_base.h +5 -25
  539. esphome/components/web_server_idf/multipart.cpp +254 -0
  540. esphome/components/web_server_idf/multipart.h +86 -0
  541. esphome/components/web_server_idf/utils.cpp +32 -0
  542. esphome/components/web_server_idf/utils.h +10 -0
  543. esphome/components/web_server_idf/web_server_idf.cpp +164 -16
  544. esphome/components/web_server_idf/web_server_idf.h +11 -10
  545. esphome/components/wiegand/wiegand.cpp +2 -2
  546. esphome/components/wifi/__init__.py +18 -0
  547. esphome/components/wifi/wifi_component.cpp +17 -22
  548. esphome/components/wifi/wifi_component.h +27 -23
  549. esphome/components/wifi/wifi_component_esp32_arduino.cpp +52 -59
  550. esphome/components/wifi/wifi_component_esp8266.cpp +46 -46
  551. esphome/components/wifi/wifi_component_esp_idf.cpp +35 -36
  552. esphome/components/wifi/wifi_component_libretiny.cpp +26 -27
  553. esphome/components/wifi/wifi_component_pico_w.cpp +3 -3
  554. esphome/components/wifi_info/wifi_info_text_sensor.cpp +6 -6
  555. esphome/components/wireguard/__init__.py +2 -11
  556. esphome/components/xiaomi_ble/xiaomi_ble.cpp +13 -1
  557. esphome/components/xiaomi_ble/xiaomi_ble.h +1 -0
  558. esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +0 -1
  559. esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +0 -1
  560. esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +0 -1
  561. esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +0 -1
  562. esphome/components/xiaomi_gcls002/xiaomi_gcls002.h +0 -1
  563. esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +0 -1
  564. esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h +0 -1
  565. esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h +0 -1
  566. esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h +0 -1
  567. esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +0 -1
  568. esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +0 -1
  569. esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +0 -1
  570. esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +0 -1
  571. esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h +0 -1
  572. esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +0 -1
  573. esphome/components/xiaomi_miscale/xiaomi_miscale.h +0 -1
  574. esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +0 -1
  575. esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h +0 -1
  576. esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +0 -1
  577. esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h +0 -1
  578. esphome/components/xiaomi_xmwsdj04mmc/__init__.py +0 -0
  579. esphome/components/xiaomi_xmwsdj04mmc/sensor.py +77 -0
  580. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +77 -0
  581. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +36 -0
  582. esphome/components/zio_ultrasonic/zio_ultrasonic.h +0 -2
  583. esphome/components/zyaura/zyaura.h +0 -1
  584. esphome/config.py +88 -22
  585. esphome/config_helpers.py +74 -1
  586. esphome/config_validation.py +12 -1
  587. esphome/const.py +65 -10
  588. esphome/core/__init__.py +18 -2
  589. esphome/core/application.cpp +169 -10
  590. esphome/core/application.h +145 -165
  591. esphome/core/area.h +19 -0
  592. esphome/core/automation.h +58 -9
  593. esphome/core/color.cpp +3 -5
  594. esphome/core/color.h +16 -16
  595. esphome/core/component.cpp +156 -22
  596. esphome/core/component.h +98 -4
  597. esphome/core/component_iterator.cpp +11 -9
  598. esphome/core/component_iterator.h +12 -10
  599. esphome/core/config.py +155 -6
  600. esphome/core/controller.cpp +4 -2
  601. esphome/core/controller.h +1 -1
  602. esphome/core/datatypes.h +2 -2
  603. esphome/core/defines.h +17 -2
  604. esphome/core/device.h +20 -0
  605. esphome/core/entity_base.cpp +20 -15
  606. esphome/core/entity_base.h +76 -0
  607. esphome/core/entity_helpers.py +168 -1
  608. esphome/core/event_pool.h +81 -0
  609. esphome/core/helpers.cpp +75 -230
  610. esphome/core/helpers.h +165 -105
  611. esphome/core/lock_free_queue.h +151 -0
  612. esphome/core/log.cpp +2 -2
  613. esphome/core/log.h +2 -0
  614. esphome/core/optional.h +5 -0
  615. esphome/core/ring_buffer.cpp +2 -2
  616. esphome/core/scheduler.cpp +275 -103
  617. esphome/core/scheduler.h +154 -17
  618. esphome/core/time.cpp +5 -5
  619. esphome/core/time.h +5 -5
  620. esphome/cpp_generator.py +17 -0
  621. esphome/cpp_helpers.py +0 -22
  622. esphome/cpp_types.py +3 -1
  623. esphome/dashboard/entries.py +1 -1
  624. esphome/dashboard/util/text.py +5 -21
  625. esphome/dashboard/web_server.py +9 -1
  626. esphome/helpers.py +47 -0
  627. esphome/loader.py +15 -1
  628. esphome/pins.py +14 -8
  629. esphome/platformio_api.py +2 -0
  630. esphome/wizard.py +17 -4
  631. esphome/writer.py +44 -3
  632. esphome/yaml_util.py +0 -2
  633. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/METADATA +10 -9
  634. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/RECORD +639 -580
  635. esphome/components/api/api_pb2_size.h +0 -361
  636. esphome/components/esp32_ble/ble_event_pool.h +0 -72
  637. esphome/components/esp32_ble/queue.h +0 -85
  638. esphome/components/esp32_hall/esp32_hall.cpp +0 -25
  639. esphome/components/esp32_hall/esp32_hall.h +0 -23
  640. esphome/components/esp32_touch/esp32_touch.cpp +0 -355
  641. esphome/components/ld2410/button/reset_button.cpp +0 -9
  642. esphome/components/ld2450/button/reset_button.cpp +0 -9
  643. esphome/components/openthread/tlv.py +0 -65
  644. /esphome/{dashboard/enum.py → enum.py} +0 -0
  645. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/WHEEL +0 -0
  646. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/entry_points.txt +0 -0
  647. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/licenses/LICENSE +0 -0
  648. {esphome-2025.6.2.dist-info → esphome-2025.7.0.dist-info}/top_level.txt +0 -0
@@ -28,8 +28,32 @@
28
28
  namespace esphome {
29
29
  namespace api {
30
30
 
31
+ // Read a maximum of 5 messages per loop iteration to prevent starving other components.
32
+ // This is a balance between API responsiveness and allowing other components to run.
33
+ // Since each message could contain multiple protobuf messages when using packet batching,
34
+ // this limits the number of messages processed, not the number of TCP packets.
35
+ static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
36
+ static constexpr uint8_t MAX_PING_RETRIES = 60;
37
+ static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
38
+ static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
39
+
31
40
  static const char *const TAG = "api.connection";
32
- static const int ESP32_CAMERA_STOP_STREAM = 5000;
41
+ #ifdef USE_CAMERA
42
+ static const int CAMERA_STOP_STREAM = 5000;
43
+ #endif
44
+
45
+ // Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object
46
+ #define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
47
+ entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
48
+ if ((entity_var) == nullptr) \
49
+ return; \
50
+ auto call = (entity_var)->make_call();
51
+
52
+ // Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found
53
+ #define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
54
+ entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
55
+ if ((entity_var) == nullptr) \
56
+ return;
33
57
 
34
58
  APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
35
59
  : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
@@ -47,6 +71,11 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
47
71
  #else
48
72
  #error "No frame helper defined"
49
73
  #endif
74
+ #ifdef USE_CAMERA
75
+ if (camera::Camera::instance() != nullptr) {
76
+ this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
77
+ }
78
+ #endif
50
79
  }
51
80
 
52
81
  uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
@@ -54,15 +83,11 @@ uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_
54
83
  void APIConnection::start() {
55
84
  this->last_traffic_ = App.get_loop_component_start_time();
56
85
 
57
- // Set next_ping_retry_ to prevent immediate ping
58
- // This ensures the first ping happens after the keepalive period
59
- this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
60
-
61
86
  APIError err = this->helper_->init();
62
87
  if (err != APIError::OK) {
63
88
  on_fatal_error();
64
- ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
65
- errno);
89
+ ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->get_client_combined_info().c_str(),
90
+ api_error_to_str(err), errno);
66
91
  return;
67
92
  }
68
93
  this->client_info_ = helper_->getpeername();
@@ -84,104 +109,100 @@ APIConnection::~APIConnection() {
84
109
  }
85
110
 
86
111
  void APIConnection::loop() {
87
- if (this->remove_)
88
- return;
89
-
90
- if (!network::is_connected()) {
91
- // when network is disconnected force disconnect immediately
92
- // don't wait for timeout
93
- this->on_fatal_error();
94
- ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->client_combined_info_.c_str());
95
- return;
96
- }
97
- if (this->next_close_) {
112
+ if (this->flags_.next_close) {
98
113
  // requested a disconnect
99
114
  this->helper_->close();
100
- this->remove_ = true;
115
+ this->flags_.remove = true;
101
116
  return;
102
117
  }
103
118
 
104
119
  APIError err = this->helper_->loop();
105
120
  if (err != APIError::OK) {
106
121
  on_fatal_error();
107
- ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
122
+ ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(),
108
123
  api_error_to_str(err), errno);
109
124
  return;
110
125
  }
111
126
 
127
+ const uint32_t now = App.get_loop_component_start_time();
112
128
  // Check if socket has data ready before attempting to read
113
129
  if (this->helper_->is_socket_ready()) {
114
- ReadPacketBuffer buffer;
115
- err = this->helper_->read_packet(&buffer);
116
- if (err == APIError::WOULD_BLOCK) {
117
- // pass
118
- } else if (err != APIError::OK) {
119
- on_fatal_error();
120
- if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
121
- ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
122
- } else if (err == APIError::CONNECTION_CLOSED) {
123
- ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
124
- } else {
125
- ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
126
- errno);
127
- }
128
- return;
129
- } else {
130
- this->last_traffic_ = App.get_loop_component_start_time();
131
- // read a packet
132
- if (buffer.data_len > 0) {
133
- this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
130
+ // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
131
+ for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
132
+ ReadPacketBuffer buffer;
133
+ err = this->helper_->read_packet(&buffer);
134
+ if (err == APIError::WOULD_BLOCK) {
135
+ // No more data available
136
+ break;
137
+ } else if (err != APIError::OK) {
138
+ on_fatal_error();
139
+ if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
140
+ ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
141
+ } else if (err == APIError::CONNECTION_CLOSED) {
142
+ ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
143
+ } else {
144
+ ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
145
+ api_error_to_str(err), errno);
146
+ }
147
+ return;
134
148
  } else {
135
- this->read_message(0, buffer.type, nullptr);
149
+ this->last_traffic_ = now;
150
+ // read a packet
151
+ if (buffer.data_len > 0) {
152
+ this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
153
+ } else {
154
+ this->read_message(0, buffer.type, nullptr);
155
+ }
156
+ if (this->flags_.remove)
157
+ return;
136
158
  }
137
- if (this->remove_)
138
- return;
139
159
  }
140
160
  }
141
161
 
142
- // Process deferred batch if scheduled
143
- if (this->deferred_batch_.batch_scheduled &&
144
- App.get_loop_component_start_time() - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
162
+ // Process deferred batch if scheduled and timer has expired
163
+ if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
145
164
  this->process_batch_();
146
165
  }
147
166
 
148
- if (!this->list_entities_iterator_.completed())
149
- this->list_entities_iterator_.advance();
150
- if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
151
- this->initial_state_iterator_.advance();
167
+ if (!this->list_entities_iterator_.completed()) {
168
+ this->process_iterator_batch_(this->list_entities_iterator_);
169
+ } else if (!this->initial_state_iterator_.completed()) {
170
+ this->process_iterator_batch_(this->initial_state_iterator_);
152
171
 
153
- static uint8_t max_ping_retries = 60;
154
- static uint16_t ping_retry_interval = 1000;
155
- const uint32_t now = App.get_loop_component_start_time();
156
- if (this->sent_ping_) {
172
+ // If we've completed initial states, process any remaining and clear the flag
173
+ if (this->initial_state_iterator_.completed()) {
174
+ // Process any remaining batched messages immediately
175
+ if (!this->deferred_batch_.empty()) {
176
+ this->process_batch_();
177
+ }
178
+ // Now that everything is sent, enable immediate sending for future state changes
179
+ this->flags_.should_try_send_immediately = true;
180
+ }
181
+ }
182
+
183
+ if (this->flags_.sent_ping) {
157
184
  // Disconnect if not responded within 2.5*keepalive
158
- if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
185
+ if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
159
186
  on_fatal_error();
160
- ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->client_combined_info_.c_str());
187
+ ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
161
188
  }
162
- } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
189
+ } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
163
190
  ESP_LOGVV(TAG, "Sending keepalive PING");
164
- this->sent_ping_ = this->send_message(PingRequest());
165
- if (!this->sent_ping_) {
166
- this->next_ping_retry_ = now + ping_retry_interval;
167
- this->ping_retries_++;
168
- std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);",
169
- this->client_combined_info_.c_str(), this->ping_retries_);
170
- if (this->ping_retries_ >= max_ping_retries) {
171
- on_fatal_error();
172
- ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str());
173
- } else if (this->ping_retries_ >= 10) {
174
- ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
175
- } else {
176
- ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
177
- }
191
+ this->flags_.sent_ping = this->send_message(PingRequest());
192
+ if (!this->flags_.sent_ping) {
193
+ // If we can't send the ping request directly (tx_buffer full),
194
+ // schedule it at the front of the batch so it will be sent with priority
195
+ ESP_LOGW(TAG, "Buffer full, ping queued");
196
+ this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
197
+ PingRequest::ESTIMATED_SIZE);
198
+ this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
178
199
  }
179
200
  }
180
201
 
181
- #ifdef USE_ESP32_CAMERA
182
- if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
183
- uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_.available());
184
- bool done = this->image_reader_.available() == to_send;
202
+ #ifdef USE_CAMERA
203
+ if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
204
+ uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
205
+ bool done = this->image_reader_->available() == to_send;
185
206
  uint32_t msg_size = 0;
186
207
  ProtoSize::add_fixed_field<4>(msg_size, 1, true);
187
208
  // partial message size calculated manually since its a special case
@@ -191,28 +212,26 @@ void APIConnection::loop() {
191
212
 
192
213
  auto buffer = this->create_buffer(msg_size);
193
214
  // fixed32 key = 1;
194
- buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
215
+ buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash());
195
216
  // bytes data = 2;
196
- buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
217
+ buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send);
197
218
  // bool done = 3;
198
219
  buffer.encode_bool(3, done);
199
220
 
200
- bool success = this->send_buffer(buffer, 44);
221
+ bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
201
222
 
202
223
  if (success) {
203
- this->image_reader_.consume_data(to_send);
204
- }
205
- if (success && done) {
206
- this->image_reader_.return_image();
224
+ this->image_reader_->consume_data(to_send);
225
+ if (done) {
226
+ this->image_reader_->return_image();
227
+ }
207
228
  }
208
229
  }
209
230
  #endif
210
231
 
211
- if (state_subs_at_ != -1) {
232
+ if (state_subs_at_ >= 0) {
212
233
  const auto &subs = this->parent_->get_state_subs();
213
- if (state_subs_at_ >= (int) subs.size()) {
214
- state_subs_at_ = -1;
215
- } else {
234
+ if (state_subs_at_ < static_cast<int>(subs.size())) {
216
235
  auto &it = subs[state_subs_at_];
217
236
  SubscribeHomeAssistantStateResponse resp;
218
237
  resp.entity_id = it.entity_id;
@@ -221,6 +240,8 @@ void APIConnection::loop() {
221
240
  if (this->send_message(resp)) {
222
241
  state_subs_at_++;
223
242
  }
243
+ } else {
244
+ state_subs_at_ = -1;
224
245
  }
225
246
  }
226
247
  }
@@ -233,20 +254,28 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
233
254
  // remote initiated disconnect_client
234
255
  // don't close yet, we still need to send the disconnect response
235
256
  // close will happen on next loop
236
- ESP_LOGD(TAG, "%s disconnected", this->client_combined_info_.c_str());
237
- this->next_close_ = true;
257
+ ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
258
+ this->flags_.next_close = true;
238
259
  DisconnectResponse resp;
239
260
  return resp;
240
261
  }
241
262
  void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
242
263
  this->helper_->close();
243
- this->remove_ = true;
264
+ this->flags_.remove = true;
244
265
  }
245
266
 
246
267
  // Encodes a message to the buffer and returns the total number of bytes used,
247
268
  // including header and footer overhead. Returns 0 if the message doesn't fit.
248
- uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
269
+ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
249
270
  uint32_t remaining_size, bool is_single) {
271
+ #ifdef HAS_PROTO_MESSAGE_DUMP
272
+ // If in log-only mode, just log and return
273
+ if (conn->flags_.log_only_mode) {
274
+ conn->log_send_message_(msg.message_name(), msg.dump());
275
+ return 1; // Return non-zero to indicate "success" for logging
276
+ }
277
+ #endif
278
+
250
279
  // Calculate size
251
280
  uint32_t calculated_size = 0;
252
281
  msg.calculate_size(calculated_size);
@@ -287,12 +316,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
287
316
 
288
317
  #ifdef USE_BINARY_SENSOR
289
318
  bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
290
- return this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
291
- BinarySensorStateResponse::MESSAGE_TYPE);
292
- }
293
- void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
294
- this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_info,
295
- ListEntitiesBinarySensorResponse::MESSAGE_TYPE);
319
+ return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
320
+ BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
296
321
  }
297
322
 
298
323
  uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -319,10 +344,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
319
344
 
320
345
  #ifdef USE_COVER
321
346
  bool APIConnection::send_cover_state(cover::Cover *cover) {
322
- return this->schedule_message_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE);
323
- }
324
- void APIConnection::send_cover_info(cover::Cover *cover) {
325
- this->schedule_message_(cover, &APIConnection::try_send_cover_info, ListEntitiesCoverResponse::MESSAGE_TYPE);
347
+ return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
348
+ CoverStateResponse::ESTIMATED_SIZE);
326
349
  }
327
350
  uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
328
351
  bool is_single) {
@@ -353,11 +376,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
353
376
  return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
354
377
  }
355
378
  void APIConnection::cover_command(const CoverCommandRequest &msg) {
356
- cover::Cover *cover = App.get_cover_by_key(msg.key);
357
- if (cover == nullptr)
358
- return;
359
-
360
- auto call = cover->make_call();
379
+ ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
361
380
  if (msg.has_legacy_command) {
362
381
  switch (msg.legacy_command) {
363
382
  case enums::LEGACY_COVER_COMMAND_OPEN:
@@ -383,10 +402,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
383
402
 
384
403
  #ifdef USE_FAN
385
404
  bool APIConnection::send_fan_state(fan::Fan *fan) {
386
- return this->schedule_message_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE);
387
- }
388
- void APIConnection::send_fan_info(fan::Fan *fan) {
389
- this->schedule_message_(fan, &APIConnection::try_send_fan_info, ListEntitiesFanResponse::MESSAGE_TYPE);
405
+ return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
406
+ FanStateResponse::ESTIMATED_SIZE);
390
407
  }
391
408
  uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
392
409
  bool is_single) {
@@ -422,11 +439,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
422
439
  return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
423
440
  }
424
441
  void APIConnection::fan_command(const FanCommandRequest &msg) {
425
- fan::Fan *fan = App.get_fan_by_key(msg.key);
426
- if (fan == nullptr)
427
- return;
428
-
429
- auto call = fan->make_call();
442
+ ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
430
443
  if (msg.has_state)
431
444
  call.set_state(msg.state);
432
445
  if (msg.has_oscillating)
@@ -445,10 +458,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
445
458
 
446
459
  #ifdef USE_LIGHT
447
460
  bool APIConnection::send_light_state(light::LightState *light) {
448
- return this->schedule_message_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE);
449
- }
450
- void APIConnection::send_light_info(light::LightState *light) {
451
- this->schedule_message_(light, &APIConnection::try_send_light_info, ListEntitiesLightResponse::MESSAGE_TYPE);
461
+ return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
462
+ LightStateResponse::ESTIMATED_SIZE);
452
463
  }
453
464
  uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
454
465
  bool is_single) {
@@ -502,11 +513,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
502
513
  return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
503
514
  }
504
515
  void APIConnection::light_command(const LightCommandRequest &msg) {
505
- light::LightState *light = App.get_light_by_key(msg.key);
506
- if (light == nullptr)
507
- return;
508
-
509
- auto call = light->make_call();
516
+ ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
510
517
  if (msg.has_state)
511
518
  call.set_state(msg.state);
512
519
  if (msg.has_brightness)
@@ -540,10 +547,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
540
547
 
541
548
  #ifdef USE_SENSOR
542
549
  bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
543
- return this->schedule_message_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE);
544
- }
545
- void APIConnection::send_sensor_info(sensor::Sensor *sensor) {
546
- this->schedule_message_(sensor, &APIConnection::try_send_sensor_info, ListEntitiesSensorResponse::MESSAGE_TYPE);
550
+ return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
551
+ SensorStateResponse::ESTIMATED_SIZE);
547
552
  }
548
553
 
549
554
  uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -575,10 +580,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
575
580
 
576
581
  #ifdef USE_SWITCH
577
582
  bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
578
- return this->schedule_message_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE);
579
- }
580
- void APIConnection::send_switch_info(switch_::Switch *a_switch) {
581
- this->schedule_message_(a_switch, &APIConnection::try_send_switch_info, ListEntitiesSwitchResponse::MESSAGE_TYPE);
583
+ return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
584
+ SwitchStateResponse::ESTIMATED_SIZE);
582
585
  }
583
586
 
584
587
  uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -601,9 +604,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
601
604
  return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
602
605
  }
603
606
  void APIConnection::switch_command(const SwitchCommandRequest &msg) {
604
- switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
605
- if (a_switch == nullptr)
606
- return;
607
+ ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
607
608
 
608
609
  if (msg.state) {
609
610
  a_switch->turn_on();
@@ -615,12 +616,8 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
615
616
 
616
617
  #ifdef USE_TEXT_SENSOR
617
618
  bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
618
- return this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_state,
619
- TextSensorStateResponse::MESSAGE_TYPE);
620
- }
621
- void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
622
- this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_info,
623
- ListEntitiesTextSensorResponse::MESSAGE_TYPE);
619
+ return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
620
+ TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
624
621
  }
625
622
 
626
623
  uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -647,7 +644,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
647
644
 
648
645
  #ifdef USE_CLIMATE
649
646
  bool APIConnection::send_climate_state(climate::Climate *climate) {
650
- return this->schedule_message_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE);
647
+ return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
648
+ ClimateStateResponse::ESTIMATED_SIZE);
651
649
  }
652
650
  uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
653
651
  bool is_single) {
@@ -682,9 +680,6 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
682
680
  resp.target_humidity = climate->target_humidity;
683
681
  return encode_message_to_buffer(resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
684
682
  }
685
- void APIConnection::send_climate_info(climate::Climate *climate) {
686
- this->schedule_message_(climate, &APIConnection::try_send_climate_info, ListEntitiesClimateResponse::MESSAGE_TYPE);
687
- }
688
683
  uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
689
684
  bool is_single) {
690
685
  auto *climate = static_cast<climate::Climate *>(entity);
@@ -719,11 +714,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
719
714
  return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
720
715
  }
721
716
  void APIConnection::climate_command(const ClimateCommandRequest &msg) {
722
- climate::Climate *climate = App.get_climate_by_key(msg.key);
723
- if (climate == nullptr)
724
- return;
725
-
726
- auto call = climate->make_call();
717
+ ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
727
718
  if (msg.has_mode)
728
719
  call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
729
720
  if (msg.has_target_temperature)
@@ -750,10 +741,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
750
741
 
751
742
  #ifdef USE_NUMBER
752
743
  bool APIConnection::send_number_state(number::Number *number) {
753
- return this->schedule_message_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE);
754
- }
755
- void APIConnection::send_number_info(number::Number *number) {
756
- this->schedule_message_(number, &APIConnection::try_send_number_info, ListEntitiesNumberResponse::MESSAGE_TYPE);
744
+ return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
745
+ NumberStateResponse::ESTIMATED_SIZE);
757
746
  }
758
747
 
759
748
  uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -781,11 +770,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
781
770
  return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
782
771
  }
783
772
  void APIConnection::number_command(const NumberCommandRequest &msg) {
784
- number::Number *number = App.get_number_by_key(msg.key);
785
- if (number == nullptr)
786
- return;
787
-
788
- auto call = number->make_call();
773
+ ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
789
774
  call.set_value(msg.state);
790
775
  call.perform();
791
776
  }
@@ -793,7 +778,8 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
793
778
 
794
779
  #ifdef USE_DATETIME_DATE
795
780
  bool APIConnection::send_date_state(datetime::DateEntity *date) {
796
- return this->schedule_message_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE);
781
+ return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
782
+ DateStateResponse::ESTIMATED_SIZE);
797
783
  }
798
784
  uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
799
785
  bool is_single) {
@@ -806,9 +792,6 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c
806
792
  fill_entity_state_base(date, resp);
807
793
  return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
808
794
  }
809
- void APIConnection::send_date_info(datetime::DateEntity *date) {
810
- this->schedule_message_(date, &APIConnection::try_send_date_info, ListEntitiesDateResponse::MESSAGE_TYPE);
811
- }
812
795
  uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
813
796
  bool is_single) {
814
797
  auto *date = static_cast<datetime::DateEntity *>(entity);
@@ -818,11 +801,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co
818
801
  return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
819
802
  }
820
803
  void APIConnection::date_command(const DateCommandRequest &msg) {
821
- datetime::DateEntity *date = App.get_date_by_key(msg.key);
822
- if (date == nullptr)
823
- return;
824
-
825
- auto call = date->make_call();
804
+ ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
826
805
  call.set_date(msg.year, msg.month, msg.day);
827
806
  call.perform();
828
807
  }
@@ -830,7 +809,8 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
830
809
 
831
810
  #ifdef USE_DATETIME_TIME
832
811
  bool APIConnection::send_time_state(datetime::TimeEntity *time) {
833
- return this->schedule_message_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE);
812
+ return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
813
+ TimeStateResponse::ESTIMATED_SIZE);
834
814
  }
835
815
  uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
836
816
  bool is_single) {
@@ -843,9 +823,6 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c
843
823
  fill_entity_state_base(time, resp);
844
824
  return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
845
825
  }
846
- void APIConnection::send_time_info(datetime::TimeEntity *time) {
847
- this->schedule_message_(time, &APIConnection::try_send_time_info, ListEntitiesTimeResponse::MESSAGE_TYPE);
848
- }
849
826
  uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
850
827
  bool is_single) {
851
828
  auto *time = static_cast<datetime::TimeEntity *>(entity);
@@ -855,11 +832,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co
855
832
  return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
856
833
  }
857
834
  void APIConnection::time_command(const TimeCommandRequest &msg) {
858
- datetime::TimeEntity *time = App.get_time_by_key(msg.key);
859
- if (time == nullptr)
860
- return;
861
-
862
- auto call = time->make_call();
835
+ ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
863
836
  call.set_time(msg.hour, msg.minute, msg.second);
864
837
  call.perform();
865
838
  }
@@ -867,8 +840,8 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
867
840
 
868
841
  #ifdef USE_DATETIME_DATETIME
869
842
  bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
870
- return this->schedule_message_(datetime, &APIConnection::try_send_datetime_state,
871
- DateTimeStateResponse::MESSAGE_TYPE);
843
+ return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
844
+ DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
872
845
  }
873
846
  uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
874
847
  bool is_single) {
@@ -882,9 +855,6 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio
882
855
  fill_entity_state_base(datetime, resp);
883
856
  return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
884
857
  }
885
- void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
886
- this->schedule_message_(datetime, &APIConnection::try_send_datetime_info, ListEntitiesDateTimeResponse::MESSAGE_TYPE);
887
- }
888
858
  uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
889
859
  bool is_single) {
890
860
  auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
@@ -894,11 +864,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection
894
864
  return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
895
865
  }
896
866
  void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
897
- datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
898
- if (datetime == nullptr)
899
- return;
900
-
901
- auto call = datetime->make_call();
867
+ ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
902
868
  call.set_datetime(msg.epoch_seconds);
903
869
  call.perform();
904
870
  }
@@ -906,10 +872,8 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
906
872
 
907
873
  #ifdef USE_TEXT
908
874
  bool APIConnection::send_text_state(text::Text *text) {
909
- return this->schedule_message_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE);
910
- }
911
- void APIConnection::send_text_info(text::Text *text) {
912
- this->schedule_message_(text, &APIConnection::try_send_text_info, ListEntitiesTextResponse::MESSAGE_TYPE);
875
+ return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
876
+ TextStateResponse::ESTIMATED_SIZE);
913
877
  }
914
878
 
915
879
  uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -935,11 +899,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
935
899
  return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
936
900
  }
937
901
  void APIConnection::text_command(const TextCommandRequest &msg) {
938
- text::Text *text = App.get_text_by_key(msg.key);
939
- if (text == nullptr)
940
- return;
941
-
942
- auto call = text->make_call();
902
+ ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
943
903
  call.set_value(msg.state);
944
904
  call.perform();
945
905
  }
@@ -947,10 +907,8 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
947
907
 
948
908
  #ifdef USE_SELECT
949
909
  bool APIConnection::send_select_state(select::Select *select) {
950
- return this->schedule_message_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE);
951
- }
952
- void APIConnection::send_select_info(select::Select *select) {
953
- this->schedule_message_(select, &APIConnection::try_send_select_info, ListEntitiesSelectResponse::MESSAGE_TYPE);
910
+ return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
911
+ SelectStateResponse::ESTIMATED_SIZE);
954
912
  }
955
913
 
956
914
  uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -974,20 +932,13 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
974
932
  return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
975
933
  }
976
934
  void APIConnection::select_command(const SelectCommandRequest &msg) {
977
- select::Select *select = App.get_select_by_key(msg.key);
978
- if (select == nullptr)
979
- return;
980
-
981
- auto call = select->make_call();
935
+ ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
982
936
  call.set_option(msg.state);
983
937
  call.perform();
984
938
  }
985
939
  #endif
986
940
 
987
941
  #ifdef USE_BUTTON
988
- void esphome::api::APIConnection::send_button_info(button::Button *button) {
989
- this->schedule_message_(button, &APIConnection::try_send_button_info, ListEntitiesButtonResponse::MESSAGE_TYPE);
990
- }
991
942
  uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
992
943
  bool is_single) {
993
944
  auto *button = static_cast<button::Button *>(entity);
@@ -998,20 +949,15 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
998
949
  return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
999
950
  }
1000
951
  void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
1001
- button::Button *button = App.get_button_by_key(msg.key);
1002
- if (button == nullptr)
1003
- return;
1004
-
952
+ ENTITY_COMMAND_GET(button::Button, button, button)
1005
953
  button->press();
1006
954
  }
1007
955
  #endif
1008
956
 
1009
957
  #ifdef USE_LOCK
1010
958
  bool APIConnection::send_lock_state(lock::Lock *a_lock) {
1011
- return this->schedule_message_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE);
1012
- }
1013
- void APIConnection::send_lock_info(lock::Lock *a_lock) {
1014
- this->schedule_message_(a_lock, &APIConnection::try_send_lock_info, ListEntitiesLockResponse::MESSAGE_TYPE);
959
+ return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
960
+ LockStateResponse::ESTIMATED_SIZE);
1015
961
  }
1016
962
 
1017
963
  uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -1035,9 +981,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
1035
981
  return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1036
982
  }
1037
983
  void APIConnection::lock_command(const LockCommandRequest &msg) {
1038
- lock::Lock *a_lock = App.get_lock_by_key(msg.key);
1039
- if (a_lock == nullptr)
1040
- return;
984
+ ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
1041
985
 
1042
986
  switch (msg.command) {
1043
987
  case enums::LOCK_UNLOCK:
@@ -1055,7 +999,8 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
1055
999
 
1056
1000
  #ifdef USE_VALVE
1057
1001
  bool APIConnection::send_valve_state(valve::Valve *valve) {
1058
- return this->schedule_message_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE);
1002
+ return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
1003
+ ValveStateResponse::ESTIMATED_SIZE);
1059
1004
  }
1060
1005
  uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1061
1006
  bool is_single) {
@@ -1066,9 +1011,6 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *
1066
1011
  fill_entity_state_base(valve, resp);
1067
1012
  return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1068
1013
  }
1069
- void APIConnection::send_valve_info(valve::Valve *valve) {
1070
- this->schedule_message_(valve, &APIConnection::try_send_valve_info, ListEntitiesValveResponse::MESSAGE_TYPE);
1071
- }
1072
1014
  uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1073
1015
  bool is_single) {
1074
1016
  auto *valve = static_cast<valve::Valve *>(entity);
@@ -1083,11 +1025,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
1083
1025
  return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1084
1026
  }
1085
1027
  void APIConnection::valve_command(const ValveCommandRequest &msg) {
1086
- valve::Valve *valve = App.get_valve_by_key(msg.key);
1087
- if (valve == nullptr)
1088
- return;
1089
-
1090
- auto call = valve->make_call();
1028
+ ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
1091
1029
  if (msg.has_position)
1092
1030
  call.set_position(msg.position);
1093
1031
  if (msg.stop)
@@ -1098,8 +1036,8 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
1098
1036
 
1099
1037
  #ifdef USE_MEDIA_PLAYER
1100
1038
  bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
1101
- return this->schedule_message_(media_player, &APIConnection::try_send_media_player_state,
1102
- MediaPlayerStateResponse::MESSAGE_TYPE);
1039
+ return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
1040
+ MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
1103
1041
  }
1104
1042
  uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1105
1043
  bool is_single) {
@@ -1114,10 +1052,6 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne
1114
1052
  fill_entity_state_base(media_player, resp);
1115
1053
  return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1116
1054
  }
1117
- void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) {
1118
- this->schedule_message_(media_player, &APIConnection::try_send_media_player_info,
1119
- ListEntitiesMediaPlayerResponse::MESSAGE_TYPE);
1120
- }
1121
1055
  uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1122
1056
  bool is_single) {
1123
1057
  auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
@@ -1138,11 +1072,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
1138
1072
  return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1139
1073
  }
1140
1074
  void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
1141
- media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
1142
- if (media_player == nullptr)
1143
- return;
1144
-
1145
- auto call = media_player->make_call();
1075
+ ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
1146
1076
  if (msg.has_command) {
1147
1077
  call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
1148
1078
  }
@@ -1159,39 +1089,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
1159
1089
  }
1160
1090
  #endif
1161
1091
 
1162
- #ifdef USE_ESP32_CAMERA
1163
- void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
1164
- if (!this->state_subscription_)
1092
+ #ifdef USE_CAMERA
1093
+ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
1094
+ if (!this->flags_.state_subscription)
1165
1095
  return;
1166
- if (this->image_reader_.available())
1096
+ if (!this->image_reader_)
1167
1097
  return;
1168
- if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
1169
- image->was_requested_by(esphome::esp32_camera::IDLE))
1170
- this->image_reader_.set_image(std::move(image));
1171
- }
1172
- void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
1173
- this->schedule_message_(camera, &APIConnection::try_send_camera_info, ListEntitiesCameraResponse::MESSAGE_TYPE);
1098
+ if (this->image_reader_->available())
1099
+ return;
1100
+ if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
1101
+ this->image_reader_->set_image(std::move(image));
1174
1102
  }
1175
1103
  uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1176
1104
  bool is_single) {
1177
- auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity);
1105
+ auto *camera = static_cast<camera::Camera *>(entity);
1178
1106
  ListEntitiesCameraResponse msg;
1179
1107
  msg.unique_id = get_default_unique_id("camera", camera);
1180
1108
  fill_entity_info_base(camera, msg);
1181
1109
  return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1182
1110
  }
1183
1111
  void APIConnection::camera_image(const CameraImageRequest &msg) {
1184
- if (esp32_camera::global_esp32_camera == nullptr)
1112
+ if (camera::Camera::instance() == nullptr)
1185
1113
  return;
1186
1114
 
1187
1115
  if (msg.single)
1188
- esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
1116
+ camera::Camera::instance()->request_image(esphome::camera::API_REQUESTER);
1189
1117
  if (msg.stream) {
1190
- esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
1118
+ camera::Camera::instance()->start_stream(esphome::camera::API_REQUESTER);
1191
1119
 
1192
- App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
1193
- esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
1194
- });
1120
+ App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
1121
+ []() { camera::Camera::instance()->stop_stream(esphome::camera::API_REQUESTER); });
1195
1122
  }
1196
1123
  }
1197
1124
  #endif
@@ -1263,66 +1190,53 @@ void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequ
1263
1190
  #endif
1264
1191
 
1265
1192
  #ifdef USE_VOICE_ASSISTANT
1193
+ bool APIConnection::check_voice_assistant_api_connection_() const {
1194
+ return voice_assistant::global_voice_assistant != nullptr &&
1195
+ voice_assistant::global_voice_assistant->get_api_connection() == this;
1196
+ }
1197
+
1266
1198
  void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
1267
1199
  if (voice_assistant::global_voice_assistant != nullptr) {
1268
1200
  voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
1269
1201
  }
1270
1202
  }
1271
1203
  void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
1272
- if (voice_assistant::global_voice_assistant != nullptr) {
1273
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1274
- return;
1275
- }
1204
+ if (!this->check_voice_assistant_api_connection_()) {
1205
+ return;
1206
+ }
1276
1207
 
1277
- if (msg.error) {
1278
- voice_assistant::global_voice_assistant->failed_to_start();
1279
- return;
1280
- }
1281
- if (msg.port == 0) {
1282
- // Use API Audio
1283
- voice_assistant::global_voice_assistant->start_streaming();
1284
- } else {
1285
- struct sockaddr_storage storage;
1286
- socklen_t len = sizeof(storage);
1287
- this->helper_->getpeername((struct sockaddr *) &storage, &len);
1288
- voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
1289
- }
1208
+ if (msg.error) {
1209
+ voice_assistant::global_voice_assistant->failed_to_start();
1210
+ return;
1211
+ }
1212
+ if (msg.port == 0) {
1213
+ // Use API Audio
1214
+ voice_assistant::global_voice_assistant->start_streaming();
1215
+ } else {
1216
+ struct sockaddr_storage storage;
1217
+ socklen_t len = sizeof(storage);
1218
+ this->helper_->getpeername((struct sockaddr *) &storage, &len);
1219
+ voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
1290
1220
  }
1291
1221
  };
1292
1222
  void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
1293
- if (voice_assistant::global_voice_assistant != nullptr) {
1294
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1295
- return;
1296
- }
1297
-
1223
+ if (this->check_voice_assistant_api_connection_()) {
1298
1224
  voice_assistant::global_voice_assistant->on_event(msg);
1299
1225
  }
1300
1226
  }
1301
1227
  void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
1302
- if (voice_assistant::global_voice_assistant != nullptr) {
1303
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1304
- return;
1305
- }
1306
-
1228
+ if (this->check_voice_assistant_api_connection_()) {
1307
1229
  voice_assistant::global_voice_assistant->on_audio(msg);
1308
1230
  }
1309
1231
  };
1310
1232
  void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
1311
- if (voice_assistant::global_voice_assistant != nullptr) {
1312
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1313
- return;
1314
- }
1315
-
1233
+ if (this->check_voice_assistant_api_connection_()) {
1316
1234
  voice_assistant::global_voice_assistant->on_timer_event(msg);
1317
1235
  }
1318
1236
  };
1319
1237
 
1320
1238
  void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
1321
- if (voice_assistant::global_voice_assistant != nullptr) {
1322
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1323
- return;
1324
- }
1325
-
1239
+ if (this->check_voice_assistant_api_connection_()) {
1326
1240
  voice_assistant::global_voice_assistant->on_announce(msg);
1327
1241
  }
1328
1242
  }
@@ -1330,35 +1244,29 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
1330
1244
  VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
1331
1245
  const VoiceAssistantConfigurationRequest &msg) {
1332
1246
  VoiceAssistantConfigurationResponse resp;
1333
- if (voice_assistant::global_voice_assistant != nullptr) {
1334
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1335
- return resp;
1336
- }
1247
+ if (!this->check_voice_assistant_api_connection_()) {
1248
+ return resp;
1249
+ }
1337
1250
 
1338
- auto &config = voice_assistant::global_voice_assistant->get_configuration();
1339
- for (auto &wake_word : config.available_wake_words) {
1340
- VoiceAssistantWakeWord resp_wake_word;
1341
- resp_wake_word.id = wake_word.id;
1342
- resp_wake_word.wake_word = wake_word.wake_word;
1343
- for (const auto &lang : wake_word.trained_languages) {
1344
- resp_wake_word.trained_languages.push_back(lang);
1345
- }
1346
- resp.available_wake_words.push_back(std::move(resp_wake_word));
1347
- }
1348
- for (auto &wake_word_id : config.active_wake_words) {
1349
- resp.active_wake_words.push_back(wake_word_id);
1251
+ auto &config = voice_assistant::global_voice_assistant->get_configuration();
1252
+ for (auto &wake_word : config.available_wake_words) {
1253
+ VoiceAssistantWakeWord resp_wake_word;
1254
+ resp_wake_word.id = wake_word.id;
1255
+ resp_wake_word.wake_word = wake_word.wake_word;
1256
+ for (const auto &lang : wake_word.trained_languages) {
1257
+ resp_wake_word.trained_languages.push_back(lang);
1350
1258
  }
1351
- resp.max_active_wake_words = config.max_active_wake_words;
1259
+ resp.available_wake_words.push_back(std::move(resp_wake_word));
1260
+ }
1261
+ for (auto &wake_word_id : config.active_wake_words) {
1262
+ resp.active_wake_words.push_back(wake_word_id);
1352
1263
  }
1264
+ resp.max_active_wake_words = config.max_active_wake_words;
1353
1265
  return resp;
1354
1266
  }
1355
1267
 
1356
1268
  void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
1357
- if (voice_assistant::global_voice_assistant != nullptr) {
1358
- if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
1359
- return;
1360
- }
1361
-
1269
+ if (this->check_voice_assistant_api_connection_()) {
1362
1270
  voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
1363
1271
  }
1364
1272
  }
@@ -1367,8 +1275,9 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
1367
1275
 
1368
1276
  #ifdef USE_ALARM_CONTROL_PANEL
1369
1277
  bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1370
- return this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
1371
- AlarmControlPanelStateResponse::MESSAGE_TYPE);
1278
+ return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
1279
+ AlarmControlPanelStateResponse::MESSAGE_TYPE,
1280
+ AlarmControlPanelStateResponse::ESTIMATED_SIZE);
1372
1281
  }
1373
1282
  uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
1374
1283
  uint32_t remaining_size, bool is_single) {
@@ -1378,10 +1287,6 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A
1378
1287
  fill_entity_state_base(a_alarm_control_panel, resp);
1379
1288
  return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1380
1289
  }
1381
- void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
1382
- this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_info,
1383
- ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE);
1384
- }
1385
1290
  uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
1386
1291
  uint32_t remaining_size, bool is_single) {
1387
1292
  auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
@@ -1395,11 +1300,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
1395
1300
  is_single);
1396
1301
  }
1397
1302
  void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
1398
- alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
1399
- if (a_alarm_control_panel == nullptr)
1400
- return;
1401
-
1402
- auto call = a_alarm_control_panel->make_call();
1303
+ ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
1403
1304
  switch (msg.command) {
1404
1305
  case enums::ALARM_CONTROL_PANEL_DISARM:
1405
1306
  call.disarm();
@@ -1430,10 +1331,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
1430
1331
 
1431
1332
  #ifdef USE_EVENT
1432
1333
  void APIConnection::send_event(event::Event *event, const std::string &event_type) {
1433
- this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE);
1434
- }
1435
- void APIConnection::send_event_info(event::Event *event) {
1436
- this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
1334
+ this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
1335
+ EventResponse::ESTIMATED_SIZE);
1437
1336
  }
1438
1337
  uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
1439
1338
  uint32_t remaining_size, bool is_single) {
@@ -1458,7 +1357,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
1458
1357
 
1459
1358
  #ifdef USE_UPDATE
1460
1359
  bool APIConnection::send_update_state(update::UpdateEntity *update) {
1461
- return this->schedule_message_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE);
1360
+ return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
1361
+ UpdateStateResponse::ESTIMATED_SIZE);
1462
1362
  }
1463
1363
  uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1464
1364
  bool is_single) {
@@ -1480,9 +1380,6 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
1480
1380
  fill_entity_state_base(update, resp);
1481
1381
  return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1482
1382
  }
1483
- void APIConnection::send_update_info(update::UpdateEntity *update) {
1484
- this->schedule_message_(update, &APIConnection::try_send_update_info, ListEntitiesUpdateResponse::MESSAGE_TYPE);
1485
- }
1486
1383
  uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1487
1384
  bool is_single) {
1488
1385
  auto *update = static_cast<update::UpdateEntity *>(entity);
@@ -1493,9 +1390,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
1493
1390
  return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
1494
1391
  }
1495
1392
  void APIConnection::update_command(const UpdateCommandRequest &msg) {
1496
- update::UpdateEntity *update = App.get_update_by_key(msg.key);
1497
- if (update == nullptr)
1498
- return;
1393
+ ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
1499
1394
 
1500
1395
  switch (msg.command) {
1501
1396
  case enums::UPDATE_COMMAND_UPDATE:
@@ -1514,12 +1409,11 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
1514
1409
  }
1515
1410
  #endif
1516
1411
 
1517
- bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
1518
- if (this->log_subscription_ < level)
1412
+ bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
1413
+ if (this->flags_.log_subscription < level)
1519
1414
  return false;
1520
1415
 
1521
1416
  // Pre-calculate message size to avoid reallocations
1522
- const size_t line_length = strlen(line);
1523
1417
  uint32_t msg_size = 0;
1524
1418
 
1525
1419
  // Add size for level field (field ID 1, varint type)
@@ -1528,14 +1422,14 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
1528
1422
 
1529
1423
  // Add size for string field (field ID 3, string type)
1530
1424
  // 1 byte for field tag + size of length varint + string length
1531
- msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(line_length)) + line_length;
1425
+ msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len;
1532
1426
 
1533
1427
  // Create a pre-sized buffer
1534
1428
  auto buffer = this->create_buffer(msg_size);
1535
1429
 
1536
1430
  // Encode the message (SubscribeLogsResponse)
1537
1431
  buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
1538
- buffer.encode_string(3, line, line_length); // string message = 3
1432
+ buffer.encode_string(3, line, message_len); // string message = 3
1539
1433
 
1540
1434
  // SubscribeLogsResponse - 29
1541
1435
  return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
@@ -1544,8 +1438,7 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
1544
1438
  HelloResponse APIConnection::hello(const HelloRequest &msg) {
1545
1439
  this->client_info_ = msg.client_info;
1546
1440
  this->client_peername_ = this->helper_->getpeername();
1547
- this->client_combined_info_ = this->client_info_ + " (" + this->client_peername_ + ")";
1548
- this->helper_->set_log_info(this->client_combined_info_);
1441
+ this->helper_->set_log_info(this->get_client_combined_info());
1549
1442
  this->client_api_version_major_ = msg.api_version_major;
1550
1443
  this->client_api_version_minor_ = msg.api_version_minor;
1551
1444
  ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
@@ -1557,19 +1450,24 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
1557
1450
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
1558
1451
  resp.name = App.get_name();
1559
1452
 
1560
- this->connection_state_ = ConnectionState::CONNECTED;
1453
+ this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
1561
1454
  return resp;
1562
1455
  }
1563
1456
  ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
1564
- bool correct = this->parent_->check_password(msg.password);
1457
+ bool correct = true;
1458
+ #ifdef USE_API_PASSWORD
1459
+ correct = this->parent_->check_password(msg.password);
1460
+ #endif
1565
1461
 
1566
1462
  ConnectResponse resp;
1567
1463
  // bool invalid_password = 1;
1568
1464
  resp.invalid_password = !correct;
1569
1465
  if (correct) {
1570
- ESP_LOGD(TAG, "%s connected", this->client_combined_info_.c_str());
1571
- this->connection_state_ = ConnectionState::AUTHENTICATED;
1466
+ ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
1467
+ this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
1468
+ #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
1572
1469
  this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
1470
+ #endif
1573
1471
  #ifdef USE_HOMEASSISTANT_TIME
1574
1472
  if (homeassistant::global_homeassistant_time != nullptr) {
1575
1473
  this->send_time_request();
@@ -1580,7 +1478,11 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
1580
1478
  }
1581
1479
  DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
1582
1480
  DeviceInfoResponse resp{};
1481
+ #ifdef USE_API_PASSWORD
1583
1482
  resp.uses_password = this->parent_->uses_password();
1483
+ #else
1484
+ resp.uses_password = false;
1485
+ #endif
1584
1486
  resp.name = App.get_name();
1585
1487
  resp.friendly_name = App.get_friendly_name();
1586
1488
  resp.suggested_area = App.get_area();
@@ -1593,6 +1495,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
1593
1495
  resp.manufacturer = "Raspberry Pi";
1594
1496
  #elif defined(USE_BK72XX)
1595
1497
  resp.manufacturer = "Beken";
1498
+ #elif defined(USE_LN882X)
1499
+ resp.manufacturer = "Lightning";
1596
1500
  #elif defined(USE_RTL87XX)
1597
1501
  resp.manufacturer = "Realtek";
1598
1502
  #elif defined(USE_HOST)
@@ -1620,6 +1524,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
1620
1524
  #endif
1621
1525
  #ifdef USE_API_NOISE
1622
1526
  resp.api_encryption_supported = true;
1527
+ #endif
1528
+ #ifdef USE_DEVICES
1529
+ for (auto const &device : App.get_devices()) {
1530
+ DeviceInfo device_info;
1531
+ device_info.device_id = device->get_device_id();
1532
+ device_info.name = device->get_name();
1533
+ device_info.area_id = device->get_area_id();
1534
+ resp.devices.push_back(device_info);
1535
+ }
1536
+ #endif
1537
+ #ifdef USE_AREAS
1538
+ for (auto const &area : App.get_areas()) {
1539
+ AreaInfo area_info;
1540
+ area_info.area_id = area->get_area_id();
1541
+ area_info.name = area->get_name();
1542
+ resp.areas.push_back(area_info);
1543
+ }
1623
1544
  #endif
1624
1545
  return resp;
1625
1546
  }
@@ -1630,6 +1551,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
1630
1551
  }
1631
1552
  }
1632
1553
  }
1554
+ #ifdef USE_API_SERVICES
1633
1555
  void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
1634
1556
  bool found = false;
1635
1557
  for (auto *service : this->parent_->get_user_services()) {
@@ -1641,6 +1563,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
1641
1563
  ESP_LOGV(TAG, "Could not find service");
1642
1564
  }
1643
1565
  }
1566
+ #endif
1644
1567
  #ifdef USE_API_NOISE
1645
1568
  NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
1646
1569
  psk_t psk{};
@@ -1665,7 +1588,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
1665
1588
  state_subs_at_ = 0;
1666
1589
  }
1667
1590
  bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1668
- if (this->remove_)
1591
+ if (this->flags_.remove)
1669
1592
  return false;
1670
1593
  if (this->helper_->can_write_without_blocking())
1671
1594
  return true;
@@ -1673,7 +1596,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1673
1596
  APIError err = this->helper_->loop();
1674
1597
  if (err != APIError::OK) {
1675
1598
  on_fatal_error();
1676
- ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
1599
+ ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(),
1677
1600
  api_error_to_str(err), errno);
1678
1601
  return false;
1679
1602
  }
@@ -1684,7 +1607,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
1684
1607
  }
1685
1608
  return false;
1686
1609
  }
1687
- bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) {
1610
+ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
1688
1611
  if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
1689
1612
  return false;
1690
1613
  }
@@ -1695,10 +1618,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type)
1695
1618
  if (err != APIError::OK) {
1696
1619
  on_fatal_error();
1697
1620
  if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
1698
- ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
1621
+ ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
1699
1622
  } else {
1700
- ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
1701
- errno);
1623
+ ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(),
1624
+ api_error_to_str(err), errno);
1702
1625
  }
1703
1626
  return false;
1704
1627
  }
@@ -1707,36 +1630,45 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type)
1707
1630
  }
1708
1631
  void APIConnection::on_unauthenticated_access() {
1709
1632
  this->on_fatal_error();
1710
- ESP_LOGD(TAG, "%s requested access without authentication", this->client_combined_info_.c_str());
1633
+ ESP_LOGD(TAG, "%s requested access without authentication", this->get_client_combined_info().c_str());
1711
1634
  }
1712
1635
  void APIConnection::on_no_setup_connection() {
1713
1636
  this->on_fatal_error();
1714
- ESP_LOGD(TAG, "%s requested access without full connection", this->client_combined_info_.c_str());
1637
+ ESP_LOGD(TAG, "%s requested access without full connection", this->get_client_combined_info().c_str());
1715
1638
  }
1716
1639
  void APIConnection::on_fatal_error() {
1717
1640
  this->helper_->close();
1718
- this->remove_ = true;
1641
+ this->flags_.remove = true;
1719
1642
  }
1720
1643
 
1721
- void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
1644
+ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1645
+ uint8_t estimated_size) {
1722
1646
  // Check if we already have a message of this type for this entity
1723
1647
  // This provides deduplication per entity/message_type combination
1724
1648
  // O(n) but optimized for RAM and not performance.
1725
1649
  for (auto &item : items) {
1726
1650
  if (item.entity == entity && item.message_type == message_type) {
1727
- // Update the existing item with the new creator
1651
+ // Clean up old creator before replacing
1652
+ item.creator.cleanup(message_type);
1653
+ // Move assign the new creator
1728
1654
  item.creator = std::move(creator);
1729
1655
  return;
1730
1656
  }
1731
1657
  }
1732
1658
 
1733
1659
  // No existing item found, add new one
1734
- items.emplace_back(entity, std::move(creator), message_type);
1660
+ items.emplace_back(entity, std::move(creator), message_type, estimated_size);
1661
+ }
1662
+
1663
+ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
1664
+ uint8_t estimated_size) {
1665
+ // Insert at front for high priority messages (no deduplication check)
1666
+ items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
1735
1667
  }
1736
1668
 
1737
1669
  bool APIConnection::schedule_batch_() {
1738
- if (!this->deferred_batch_.batch_scheduled) {
1739
- this->deferred_batch_.batch_scheduled = true;
1670
+ if (!this->flags_.batch_scheduled) {
1671
+ this->flags_.batch_scheduled = true;
1740
1672
  this->deferred_batch_.batch_start_time = App.get_loop_component_start_time();
1741
1673
  }
1742
1674
  return true;
@@ -1745,14 +1677,14 @@ bool APIConnection::schedule_batch_() {
1745
1677
  ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
1746
1678
 
1747
1679
  ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
1748
- ProtoWriteBuffer result = this->prepare_message_buffer(size, this->batch_first_message_);
1749
- this->batch_first_message_ = false;
1680
+ ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
1681
+ this->flags_.batch_first_message = false;
1750
1682
  return result;
1751
1683
  }
1752
1684
 
1753
1685
  void APIConnection::process_batch_() {
1754
1686
  if (this->deferred_batch_.empty()) {
1755
- this->deferred_batch_.batch_scheduled = false;
1687
+ this->flags_.batch_scheduled = false;
1756
1688
  return;
1757
1689
  }
1758
1690
 
@@ -1762,22 +1694,28 @@ void APIConnection::process_batch_() {
1762
1694
  return;
1763
1695
  }
1764
1696
 
1765
- size_t num_items = this->deferred_batch_.items.size();
1697
+ size_t num_items = this->deferred_batch_.size();
1766
1698
 
1767
1699
  // Fast path for single message - allocate exact size needed
1768
1700
  if (num_items == 1) {
1769
- const auto &item = this->deferred_batch_.items[0];
1701
+ const auto &item = this->deferred_batch_[0];
1770
1702
 
1771
1703
  // Let the creator calculate size and encode if it fits
1772
- uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true);
1704
+ uint16_t payload_size =
1705
+ item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
1773
1706
 
1774
1707
  if (payload_size > 0 &&
1775
1708
  this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
1776
- this->deferred_batch_.clear();
1709
+ #ifdef HAS_PROTO_MESSAGE_DUMP
1710
+ // Log messages after send attempt for VV debugging
1711
+ // It's safe to use the buffer for logging at this point regardless of send result
1712
+ this->log_batch_item_(item);
1713
+ #endif
1714
+ this->clear_batch_();
1777
1715
  } else if (payload_size == 0) {
1778
1716
  // Message too large
1779
1717
  ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
1780
- this->deferred_batch_.clear();
1718
+ this->clear_batch_();
1781
1719
  }
1782
1720
  return;
1783
1721
  }
@@ -1795,8 +1733,9 @@ void APIConnection::process_batch_() {
1795
1733
 
1796
1734
  // Pre-calculate exact buffer size needed based on message types
1797
1735
  uint32_t total_estimated_size = 0;
1798
- for (const auto &item : this->deferred_batch_.items) {
1799
- total_estimated_size += get_estimated_message_size(item.message_type);
1736
+ for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
1737
+ const auto &item = this->deferred_batch_[i];
1738
+ total_estimated_size += item.estimated_size;
1800
1739
  }
1801
1740
 
1802
1741
  // Calculate total overhead for all messages
@@ -1804,7 +1743,7 @@ void APIConnection::process_batch_() {
1804
1743
 
1805
1744
  // Reserve based on estimated size (much more accurate than 24-byte worst-case)
1806
1745
  this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead);
1807
- this->batch_first_message_ = true;
1746
+ this->flags_.batch_first_message = true;
1808
1747
 
1809
1748
  size_t items_processed = 0;
1810
1749
  uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
@@ -1816,10 +1755,11 @@ void APIConnection::process_batch_() {
1816
1755
  uint32_t current_offset = 0;
1817
1756
 
1818
1757
  // Process items and encode directly to buffer
1819
- for (const auto &item : this->deferred_batch_.items) {
1758
+ for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
1759
+ const auto &item = this->deferred_batch_[i];
1820
1760
  // Try to encode message
1821
1761
  // The creator will calculate overhead to determine if the message fits
1822
- uint16_t payload_size = item.creator(item.entity, this, remaining_size, false);
1762
+ uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
1823
1763
 
1824
1764
  if (payload_size == 0) {
1825
1765
  // Message won't fit, stop processing
@@ -1833,9 +1773,9 @@ void APIConnection::process_batch_() {
1833
1773
 
1834
1774
  // Update tracking variables
1835
1775
  items_processed++;
1836
- // After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation
1776
+ // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
1837
1777
  if (items_processed == 1) {
1838
- remaining_size = MAX_PACKET_SIZE;
1778
+ remaining_size = MAX_BATCH_PACKET_SIZE;
1839
1779
  }
1840
1780
  remaining_size -= payload_size;
1841
1781
  // Calculate where the next message's header padding will start
@@ -1860,44 +1800,46 @@ void APIConnection::process_batch_() {
1860
1800
  if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
1861
1801
  on_fatal_error();
1862
1802
  if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
1863
- ESP_LOGW(TAG, "%s: Connection reset during batch write", this->client_combined_info_.c_str());
1803
+ ESP_LOGW(TAG, "%s: Connection reset during batch write", this->get_client_combined_info().c_str());
1864
1804
  } else {
1865
- ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
1866
- errno);
1805
+ ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(),
1806
+ api_error_to_str(err), errno);
1867
1807
  }
1868
1808
  }
1869
1809
 
1870
- // Handle remaining items more efficiently
1871
- if (items_processed < this->deferred_batch_.items.size()) {
1872
- // Remove processed items from the beginning
1873
- this->deferred_batch_.items.erase(this->deferred_batch_.items.begin(),
1874
- this->deferred_batch_.items.begin() + items_processed);
1810
+ #ifdef HAS_PROTO_MESSAGE_DUMP
1811
+ // Log messages after send attempt for VV debugging
1812
+ // It's safe to use the buffer for logging at this point regardless of send result
1813
+ for (size_t i = 0; i < items_processed; i++) {
1814
+ const auto &item = this->deferred_batch_[i];
1815
+ this->log_batch_item_(item);
1816
+ }
1817
+ #endif
1875
1818
 
1819
+ // Handle remaining items more efficiently
1820
+ if (items_processed < this->deferred_batch_.size()) {
1821
+ // Remove processed items from the beginning with proper cleanup
1822
+ this->deferred_batch_.remove_front(items_processed);
1876
1823
  // Reschedule for remaining items
1877
1824
  this->schedule_batch_();
1878
1825
  } else {
1879
1826
  // All items processed
1880
- this->deferred_batch_.clear();
1827
+ this->clear_batch_();
1881
1828
  }
1882
1829
  }
1883
1830
 
1884
1831
  uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1885
- bool is_single) const {
1886
- switch (message_type_) {
1887
- case 0: // Function pointer
1888
- return data_.ptr(entity, conn, remaining_size, is_single);
1889
-
1832
+ bool is_single, uint8_t message_type) const {
1890
1833
  #ifdef USE_EVENT
1891
- case EventResponse::MESSAGE_TYPE: {
1892
- auto *e = static_cast<event::Event *>(entity);
1893
- return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
1894
- }
1834
+ // Special case: EventResponse uses string pointer
1835
+ if (message_type == EventResponse::MESSAGE_TYPE) {
1836
+ auto *e = static_cast<event::Event *>(entity);
1837
+ return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
1838
+ }
1895
1839
  #endif
1896
1840
 
1897
- default:
1898
- // Should not happen, return 0 to indicate no message
1899
- return 0;
1900
- }
1841
+ // All other message types use function pointers
1842
+ return data_.function_ptr(entity, conn, remaining_size, is_single);
1901
1843
  }
1902
1844
 
1903
1845
  uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -1912,147 +1854,10 @@ uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConne
1912
1854
  return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
1913
1855
  }
1914
1856
 
1915
- uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
1916
- // Use generated ESTIMATED_SIZE constants from each message type
1917
- switch (message_type) {
1918
- #ifdef USE_BINARY_SENSOR
1919
- case BinarySensorStateResponse::MESSAGE_TYPE:
1920
- return BinarySensorStateResponse::ESTIMATED_SIZE;
1921
- case ListEntitiesBinarySensorResponse::MESSAGE_TYPE:
1922
- return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE;
1923
- #endif
1924
- #ifdef USE_SENSOR
1925
- case SensorStateResponse::MESSAGE_TYPE:
1926
- return SensorStateResponse::ESTIMATED_SIZE;
1927
- case ListEntitiesSensorResponse::MESSAGE_TYPE:
1928
- return ListEntitiesSensorResponse::ESTIMATED_SIZE;
1929
- #endif
1930
- #ifdef USE_SWITCH
1931
- case SwitchStateResponse::MESSAGE_TYPE:
1932
- return SwitchStateResponse::ESTIMATED_SIZE;
1933
- case ListEntitiesSwitchResponse::MESSAGE_TYPE:
1934
- return ListEntitiesSwitchResponse::ESTIMATED_SIZE;
1935
- #endif
1936
- #ifdef USE_TEXT_SENSOR
1937
- case TextSensorStateResponse::MESSAGE_TYPE:
1938
- return TextSensorStateResponse::ESTIMATED_SIZE;
1939
- case ListEntitiesTextSensorResponse::MESSAGE_TYPE:
1940
- return ListEntitiesTextSensorResponse::ESTIMATED_SIZE;
1941
- #endif
1942
- #ifdef USE_NUMBER
1943
- case NumberStateResponse::MESSAGE_TYPE:
1944
- return NumberStateResponse::ESTIMATED_SIZE;
1945
- case ListEntitiesNumberResponse::MESSAGE_TYPE:
1946
- return ListEntitiesNumberResponse::ESTIMATED_SIZE;
1947
- #endif
1948
- #ifdef USE_TEXT
1949
- case TextStateResponse::MESSAGE_TYPE:
1950
- return TextStateResponse::ESTIMATED_SIZE;
1951
- case ListEntitiesTextResponse::MESSAGE_TYPE:
1952
- return ListEntitiesTextResponse::ESTIMATED_SIZE;
1953
- #endif
1954
- #ifdef USE_SELECT
1955
- case SelectStateResponse::MESSAGE_TYPE:
1956
- return SelectStateResponse::ESTIMATED_SIZE;
1957
- case ListEntitiesSelectResponse::MESSAGE_TYPE:
1958
- return ListEntitiesSelectResponse::ESTIMATED_SIZE;
1959
- #endif
1960
- #ifdef USE_LOCK
1961
- case LockStateResponse::MESSAGE_TYPE:
1962
- return LockStateResponse::ESTIMATED_SIZE;
1963
- case ListEntitiesLockResponse::MESSAGE_TYPE:
1964
- return ListEntitiesLockResponse::ESTIMATED_SIZE;
1965
- #endif
1966
- #ifdef USE_EVENT
1967
- case EventResponse::MESSAGE_TYPE:
1968
- return EventResponse::ESTIMATED_SIZE;
1969
- case ListEntitiesEventResponse::MESSAGE_TYPE:
1970
- return ListEntitiesEventResponse::ESTIMATED_SIZE;
1971
- #endif
1972
- #ifdef USE_COVER
1973
- case CoverStateResponse::MESSAGE_TYPE:
1974
- return CoverStateResponse::ESTIMATED_SIZE;
1975
- case ListEntitiesCoverResponse::MESSAGE_TYPE:
1976
- return ListEntitiesCoverResponse::ESTIMATED_SIZE;
1977
- #endif
1978
- #ifdef USE_FAN
1979
- case FanStateResponse::MESSAGE_TYPE:
1980
- return FanStateResponse::ESTIMATED_SIZE;
1981
- case ListEntitiesFanResponse::MESSAGE_TYPE:
1982
- return ListEntitiesFanResponse::ESTIMATED_SIZE;
1983
- #endif
1984
- #ifdef USE_LIGHT
1985
- case LightStateResponse::MESSAGE_TYPE:
1986
- return LightStateResponse::ESTIMATED_SIZE;
1987
- case ListEntitiesLightResponse::MESSAGE_TYPE:
1988
- return ListEntitiesLightResponse::ESTIMATED_SIZE;
1989
- #endif
1990
- #ifdef USE_CLIMATE
1991
- case ClimateStateResponse::MESSAGE_TYPE:
1992
- return ClimateStateResponse::ESTIMATED_SIZE;
1993
- case ListEntitiesClimateResponse::MESSAGE_TYPE:
1994
- return ListEntitiesClimateResponse::ESTIMATED_SIZE;
1995
- #endif
1996
- #ifdef USE_ESP32_CAMERA
1997
- case ListEntitiesCameraResponse::MESSAGE_TYPE:
1998
- return ListEntitiesCameraResponse::ESTIMATED_SIZE;
1999
- #endif
2000
- #ifdef USE_BUTTON
2001
- case ListEntitiesButtonResponse::MESSAGE_TYPE:
2002
- return ListEntitiesButtonResponse::ESTIMATED_SIZE;
2003
- #endif
2004
- #ifdef USE_MEDIA_PLAYER
2005
- case MediaPlayerStateResponse::MESSAGE_TYPE:
2006
- return MediaPlayerStateResponse::ESTIMATED_SIZE;
2007
- case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE:
2008
- return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE;
2009
- #endif
2010
- #ifdef USE_ALARM_CONTROL_PANEL
2011
- case AlarmControlPanelStateResponse::MESSAGE_TYPE:
2012
- return AlarmControlPanelStateResponse::ESTIMATED_SIZE;
2013
- case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE:
2014
- return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE;
2015
- #endif
2016
- #ifdef USE_DATETIME_DATE
2017
- case DateStateResponse::MESSAGE_TYPE:
2018
- return DateStateResponse::ESTIMATED_SIZE;
2019
- case ListEntitiesDateResponse::MESSAGE_TYPE:
2020
- return ListEntitiesDateResponse::ESTIMATED_SIZE;
2021
- #endif
2022
- #ifdef USE_DATETIME_TIME
2023
- case TimeStateResponse::MESSAGE_TYPE:
2024
- return TimeStateResponse::ESTIMATED_SIZE;
2025
- case ListEntitiesTimeResponse::MESSAGE_TYPE:
2026
- return ListEntitiesTimeResponse::ESTIMATED_SIZE;
2027
- #endif
2028
- #ifdef USE_DATETIME_DATETIME
2029
- case DateTimeStateResponse::MESSAGE_TYPE:
2030
- return DateTimeStateResponse::ESTIMATED_SIZE;
2031
- case ListEntitiesDateTimeResponse::MESSAGE_TYPE:
2032
- return ListEntitiesDateTimeResponse::ESTIMATED_SIZE;
2033
- #endif
2034
- #ifdef USE_VALVE
2035
- case ValveStateResponse::MESSAGE_TYPE:
2036
- return ValveStateResponse::ESTIMATED_SIZE;
2037
- case ListEntitiesValveResponse::MESSAGE_TYPE:
2038
- return ListEntitiesValveResponse::ESTIMATED_SIZE;
2039
- #endif
2040
- #ifdef USE_UPDATE
2041
- case UpdateStateResponse::MESSAGE_TYPE:
2042
- return UpdateStateResponse::ESTIMATED_SIZE;
2043
- case ListEntitiesUpdateResponse::MESSAGE_TYPE:
2044
- return ListEntitiesUpdateResponse::ESTIMATED_SIZE;
2045
- #endif
2046
- case ListEntitiesServicesResponse::MESSAGE_TYPE:
2047
- return ListEntitiesServicesResponse::ESTIMATED_SIZE;
2048
- case ListEntitiesDoneResponse::MESSAGE_TYPE:
2049
- return ListEntitiesDoneResponse::ESTIMATED_SIZE;
2050
- case DisconnectRequest::MESSAGE_TYPE:
2051
- return DisconnectRequest::ESTIMATED_SIZE;
2052
- default:
2053
- // Fallback for unknown message types
2054
- return 24;
2055
- }
1857
+ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
1858
+ bool is_single) {
1859
+ PingRequest req;
1860
+ return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
2056
1861
  }
2057
1862
 
2058
1863
  } // namespace api