esphome 2025.5.1__py3-none-any.whl → 2025.6.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. esphome/__main__.py +20 -14
  2. esphome/components/a4988/a4988.cpp +1 -1
  3. esphome/components/absolute_humidity/absolute_humidity.cpp +6 -4
  4. esphome/components/ac_dimmer/ac_dimmer.cpp +4 -2
  5. esphome/components/adc/adc_sensor_esp32.cpp +5 -3
  6. esphome/components/adc/adc_sensor_esp8266.cpp +5 -3
  7. esphome/components/adc/adc_sensor_libretiny.cpp +5 -3
  8. esphome/components/adc/adc_sensor_rp2040.cpp +5 -3
  9. esphome/components/adc128s102/adc128s102.cpp +1 -1
  10. esphome/components/ade7880/ade7880.cpp +28 -17
  11. esphome/components/ade7953_base/ade7953_base.cpp +12 -9
  12. esphome/components/ade7953_i2c/ade7953_i2c.cpp +1 -1
  13. esphome/components/ade7953_spi/ade7953_spi.cpp +1 -1
  14. esphome/components/ads1115/ads1115.cpp +3 -5
  15. esphome/components/ads1118/ads1118.cpp +2 -1
  16. esphome/components/ags10/ags10.cpp +3 -2
  17. esphome/components/aht10/aht10.cpp +27 -29
  18. esphome/components/aic3204/aic3204.cpp +2 -2
  19. esphome/components/alarm_control_panel/__init__.py +1 -0
  20. esphome/components/am2315c/am2315c.cpp +2 -2
  21. esphome/components/am2320/am2320.cpp +2 -2
  22. esphome/components/am43/am43_base.h +1 -1
  23. esphome/components/am43/cover/am43_cover.cpp +4 -2
  24. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +4 -2
  25. esphome/components/apds9306/apds9306.cpp +8 -5
  26. esphome/components/apds9960/apds9960.cpp +2 -2
  27. esphome/components/api/__init__.py +5 -0
  28. esphome/components/api/api_connection.cpp +712 -358
  29. esphome/components/api/api_connection.h +343 -284
  30. esphome/components/api/api_frame_helper.cpp +349 -344
  31. esphome/components/api/api_frame_helper.h +121 -94
  32. esphome/components/api/api_pb2.cpp +2 -0
  33. esphome/components/api/api_pb2.h +637 -1
  34. esphome/components/api/api_pb2_service.cpp +12 -688
  35. esphome/components/api/api_pb2_service.h +53 -207
  36. esphome/components/api/api_server.cpp +71 -29
  37. esphome/components/api/api_server.h +9 -0
  38. esphome/components/api/client.py +5 -2
  39. esphome/components/api/homeassistant_service.h +1 -1
  40. esphome/components/api/list_entities.cpp +1 -1
  41. esphome/components/api/proto.cpp +1 -0
  42. esphome/components/api/proto.h +4 -3
  43. esphome/components/api/subscribe_state.cpp +8 -16
  44. esphome/components/as3935/as3935.cpp +3 -3
  45. esphome/components/as5600/as5600.cpp +9 -7
  46. esphome/components/as7341/as7341.cpp +7 -5
  47. esphome/components/at581x/at581x.cpp +13 -10
  48. esphome/components/atm90e26/atm90e26.cpp +2 -2
  49. esphome/components/atm90e32/atm90e32.cpp +3 -3
  50. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +5 -3
  51. esphome/components/bang_bang/bang_bang_climate.cpp +8 -5
  52. esphome/components/bedjet/bedjet_hub.cpp +6 -4
  53. esphome/components/beken_spi_led_strip/led_strip.cpp +11 -7
  54. esphome/components/bh1750/bh1750.cpp +2 -2
  55. esphome/components/binary_sensor/__init__.py +1 -0
  56. esphome/components/binary_sensor/automation.cpp +1 -2
  57. esphome/components/bl0906/bl0906.cpp +1 -1
  58. esphome/components/bl0942/bl0942.cpp +11 -8
  59. esphome/components/bl0942/sensor.py +1 -1
  60. esphome/components/ble_client/__init__.py +5 -1
  61. esphome/components/ble_client/output/ble_binary_output.cpp +6 -3
  62. esphome/components/ble_client/sensor/ble_sensor.cpp +9 -6
  63. esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +8 -5
  64. esphome/components/bluetooth_proxy/__init__.py +5 -1
  65. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +5 -5
  66. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +16 -14
  67. esphome/components/bme280_base/bme280_base.cpp +3 -3
  68. esphome/components/bme680/bme680.cpp +3 -3
  69. esphome/components/bme680/sensor.py +2 -2
  70. esphome/components/bme680_bsec/bme680_bsec.cpp +11 -7
  71. esphome/components/bme680_bsec/sensor.py +7 -3
  72. esphome/components/bme68x_bsec2/__init__.py +6 -5
  73. esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +17 -13
  74. esphome/components/bme68x_bsec2/sensor.py +4 -4
  75. esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp +1 -0
  76. esphome/components/bmi160/bmi160.cpp +11 -11
  77. esphome/components/bmp085/bmp085.cpp +2 -2
  78. esphome/components/bmp280_base/bmp280_base.cpp +3 -3
  79. esphome/components/bmp3xx_base/bmp3xx_base.cpp +13 -13
  80. esphome/components/bmp581/bmp581.cpp +33 -27
  81. esphome/components/bp1658cj/bp1658cj.cpp +5 -3
  82. esphome/components/bp5758d/bp5758d.cpp +1 -1
  83. esphome/components/button/__init__.py +1 -0
  84. esphome/components/canbus/canbus.cpp +1 -1
  85. esphome/components/cap1188/cap1188.cpp +6 -4
  86. esphome/components/captive_portal/captive_portal.cpp +1 -1
  87. esphome/components/ccs811/ccs811.cpp +3 -2
  88. esphome/components/cd74hc4067/cd74hc4067.cpp +1 -1
  89. esphome/components/ch422g/ch422g.cpp +2 -2
  90. esphome/components/chsc6x/chsc6x_touchscreen.cpp +6 -4
  91. esphome/components/climate/__init__.py +1 -0
  92. esphome/components/climate/climate.cpp +12 -7
  93. esphome/components/climate/climate.h +1 -1
  94. esphome/components/climate_ir/climate_ir.cpp +7 -4
  95. esphome/components/cm1106/__init__.py +1 -0
  96. esphome/components/cm1106/cm1106.cpp +112 -0
  97. esphome/components/cm1106/cm1106.h +40 -0
  98. esphome/components/cm1106/sensor.py +72 -0
  99. esphome/components/const/__init__.py +1 -0
  100. esphome/components/cover/__init__.py +1 -0
  101. esphome/components/cs5460a/cs5460a.cpp +16 -11
  102. esphome/components/cse7761/cse7761.cpp +2 -2
  103. esphome/components/cse7766/cse7766.cpp +0 -5
  104. esphome/components/cse7766/cse7766.h +5 -1
  105. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +1 -1
  106. esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +6 -3
  107. esphome/components/current_based/current_based_cover.cpp +4 -2
  108. esphome/components/dac7678/dac7678_output.cpp +4 -4
  109. esphome/components/dallas_temp/dallas_temp.cpp +2 -3
  110. esphome/components/daly_bms/daly_bms.cpp +2 -1
  111. esphome/components/dashboard_import/__init__.py +1 -2
  112. esphome/components/datetime/__init__.py +3 -1
  113. esphome/components/debug/debug_component.cpp +1 -5
  114. esphome/components/debug/debug_component.h +1 -1
  115. esphome/components/debug/debug_esp32.cpp +4 -2
  116. esphome/components/deep_sleep/deep_sleep_component.cpp +11 -5
  117. esphome/components/deep_sleep/deep_sleep_esp32.cpp +7 -5
  118. esphome/components/demo/__init__.py +206 -0
  119. esphome/components/demo/demo_alarm_control_panel.h +65 -0
  120. esphome/components/demo/demo_button.h +15 -0
  121. esphome/components/demo/demo_date.h +34 -0
  122. esphome/components/demo/demo_datetime.h +40 -0
  123. esphome/components/demo/demo_lock.h +17 -0
  124. esphome/components/demo/demo_select.h +15 -0
  125. esphome/components/demo/demo_text.h +18 -0
  126. esphome/components/demo/demo_time.h +34 -0
  127. esphome/components/demo/demo_valve.h +54 -0
  128. esphome/components/dfrobot_sen0395/commands.cpp +3 -3
  129. esphome/components/dht/dht.cpp +29 -50
  130. esphome/components/dht12/dht12.cpp +2 -2
  131. esphome/components/display/display.h +5 -3
  132. esphome/components/dps310/dps310.cpp +7 -5
  133. esphome/components/ds1307/ds1307.cpp +2 -2
  134. esphome/components/dsmr/dsmr.cpp +5 -3
  135. esphome/components/duty_cycle/duty_cycle_sensor.cpp +2 -2
  136. esphome/components/duty_time/duty_time_sensor.cpp +5 -3
  137. esphome/components/ee895/ee895.cpp +3 -3
  138. esphome/components/ektf2232/touchscreen/ektf2232.cpp +1 -1
  139. esphome/components/emc2101/emc2101.cpp +11 -9
  140. esphome/components/ens160_base/ens160_base.cpp +2 -2
  141. esphome/components/ens210/ens210.cpp +2 -2
  142. esphome/components/es7210/es7210.cpp +6 -4
  143. esphome/components/es7243e/es7243e.cpp +1 -1
  144. esphome/components/es8156/es8156.cpp +1 -1
  145. esphome/components/es8311/es8311.cpp +8 -6
  146. esphome/components/es8388/__init__.py +0 -0
  147. esphome/components/es8388/audio_dac.py +26 -0
  148. esphome/components/es8388/es8388.cpp +289 -0
  149. esphome/components/es8388/es8388.h +81 -0
  150. esphome/components/es8388/es8388_const.h +83 -0
  151. esphome/components/es8388/select/__init__.py +47 -0
  152. esphome/components/es8388/select/adc_input_mic_select.cpp +12 -0
  153. esphome/components/es8388/select/adc_input_mic_select.h +15 -0
  154. esphome/components/es8388/select/dac_output_select.cpp +12 -0
  155. esphome/components/es8388/select/dac_output_select.h +15 -0
  156. esphome/components/esp32/__init__.py +88 -20
  157. esphome/components/esp32/boards.py +208 -125
  158. esphome/components/esp32/const.py +6 -0
  159. esphome/components/esp32/gpio.py +14 -1
  160. esphome/components/esp32/gpio_esp32_c5.py +45 -0
  161. esphome/components/esp32/gpio_esp32_p4.py +43 -0
  162. esphome/components/esp32/preferences.cpp +7 -7
  163. esphome/components/esp32_ble/__init__.py +115 -0
  164. esphome/components/esp32_ble/ble.cpp +11 -9
  165. esphome/components/esp32_ble/ble_uuid.h +1 -1
  166. esphome/components/esp32_ble_client/ble_client_base.cpp +4 -2
  167. esphome/components/esp32_ble_server/__init__.py +4 -1
  168. esphome/components/esp32_ble_server/ble_characteristic.cpp +1 -0
  169. esphome/components/esp32_ble_server/ble_server.h +0 -1
  170. esphome/components/esp32_ble_tracker/__init__.py +6 -2
  171. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +12 -9
  172. esphome/components/esp32_camera/esp32_camera.cpp +35 -27
  173. esphome/components/esp32_camera_web_server/camera_web_server.cpp +4 -2
  174. esphome/components/esp32_dac/esp32_dac.cpp +2 -2
  175. esphome/components/esp32_improv/__init__.py +5 -1
  176. esphome/components/esp32_improv/esp32_improv_component.cpp +2 -2
  177. esphome/components/esp32_rmt_led_strip/led_strip.cpp +11 -7
  178. esphome/components/esp32_rmt_led_strip/light.py +5 -1
  179. esphome/components/esp32_touch/esp32_touch.cpp +12 -8
  180. esphome/components/esp8266/gpio.cpp +10 -1
  181. esphome/components/esp8266/preferences.cpp +6 -6
  182. esphome/components/esp8266_pwm/esp8266_pwm.cpp +3 -3
  183. esphome/components/esp_ldo/__init__.py +91 -0
  184. esphome/components/esp_ldo/esp_ldo.cpp +43 -0
  185. esphome/components/esp_ldo/esp_ldo.h +43 -0
  186. esphome/components/esphome/ota/ota_esphome.cpp +15 -8
  187. esphome/components/ethernet/ethernet_component.cpp +38 -26
  188. esphome/components/ethernet/ethernet_component.h +1 -1
  189. esphome/components/event/__init__.py +1 -0
  190. esphome/components/exposure_notifications/exposure_notifications.cpp +1 -1
  191. esphome/components/ezo/ezo.cpp +1 -1
  192. esphome/components/ezo_pmp/ezo_pmp.cpp +1 -1
  193. esphome/components/factory_reset/button/factory_reset_button.cpp +1 -1
  194. esphome/components/factory_reset/switch/factory_reset_switch.cpp +1 -1
  195. esphome/components/fan/__init__.py +1 -0
  196. esphome/components/fan/fan.cpp +4 -2
  197. esphome/components/fastled_base/fastled_light.cpp +7 -5
  198. esphome/components/fingerprint_grow/fingerprint_grow.cpp +8 -6
  199. esphome/components/fs3000/fs3000.cpp +1 -1
  200. esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp +7 -4
  201. esphome/components/ft63x6/ft63x6.cpp +5 -3
  202. esphome/components/gcja5/gcja5.cpp +0 -12
  203. esphome/components/gcja5/gcja5.h +8 -3
  204. esphome/components/gdk101/gdk101.cpp +2 -2
  205. esphome/components/globals/globals_component.h +13 -10
  206. esphome/components/gp2y1010au0f/gp2y1010au0f.cpp +4 -2
  207. esphome/components/gp8403/gp8403.cpp +4 -2
  208. esphome/components/gp8403/output/gp8403_output.cpp +4 -2
  209. esphome/components/gpio/one_wire/gpio_one_wire.cpp +2 -2
  210. esphome/components/gpio/output/gpio_binary_output.cpp +1 -1
  211. esphome/components/gpio/switch/gpio_switch.cpp +1 -1
  212. esphome/components/graphical_display_menu/graphical_display_menu.cpp +12 -8
  213. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -6
  214. esphome/components/grove_tb6612fng/grove_tb6612fng.cpp +1 -1
  215. esphome/components/grove_tb6612fng/grove_tb6612fng.h +1 -1
  216. esphome/components/growatt_solar/growatt_solar.cpp +6 -3
  217. esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
  218. esphome/components/haier/haier_base.cpp +2 -2
  219. esphome/components/haier/hon_climate.cpp +13 -6
  220. esphome/components/havells_solar/havells_solar.cpp +5 -2
  221. esphome/components/hbridge/switch/hbridge_switch.cpp +1 -1
  222. esphome/components/hdc1080/hdc1080.cpp +2 -2
  223. esphome/components/he60r/he60r.cpp +4 -2
  224. esphome/components/hlw8012/hlw8012.cpp +6 -4
  225. esphome/components/hm3301/hm3301.cpp +2 -2
  226. esphome/components/hmc5883l/hmc5883l.cpp +2 -2
  227. esphome/components/homeassistant/time/homeassistant_time.cpp +4 -2
  228. esphome/components/honeywell_hih_i2c/honeywell_hih.cpp +4 -4
  229. esphome/components/honeywellabp/honeywellabp.cpp +4 -2
  230. esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +8 -6
  231. esphome/components/hte501/hte501.cpp +3 -2
  232. esphome/components/http_request/__init__.py +2 -2
  233. esphome/components/http_request/http_request.cpp +7 -5
  234. esphome/components/http_request/http_request_idf.cpp +4 -2
  235. esphome/components/http_request/ota/ota_http_request.cpp +1 -1
  236. esphome/components/htu21d/htu21d.cpp +2 -2
  237. esphome/components/htu31d/htu31d.cpp +2 -2
  238. esphome/components/hx711/hx711.cpp +2 -2
  239. esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +5 -3
  240. esphome/components/hyt271/hyt271.cpp +2 -2
  241. esphome/components/i2c/i2c_bus_arduino.cpp +14 -11
  242. esphome/components/i2c/i2c_bus_esp_idf.cpp +13 -11
  243. esphome/components/i2s_audio/__init__.py +2 -0
  244. esphome/components/i2s_audio/i2s_audio.cpp +3 -4
  245. esphome/components/i2s_audio/i2s_audio.h +1 -1
  246. esphome/components/i2s_audio/media_player/__init__.py +1 -1
  247. esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +5 -3
  248. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +54 -64
  249. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +4 -0
  250. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  251. esphome/components/iaqcore/iaqcore.cpp +3 -3
  252. esphome/components/ili9xxx/ili9xxx_display.cpp +12 -7
  253. esphome/components/ili9xxx/ili9xxx_display.h +1 -1
  254. esphome/components/image/image.cpp +1 -0
  255. esphome/components/ina219/ina219.cpp +2 -2
  256. esphome/components/ina226/ina226.cpp +8 -5
  257. esphome/components/ina260/ina260.cpp +1 -1
  258. esphome/components/ina2xx_base/ina2xx_base.cpp +13 -9
  259. esphome/components/ina3221/ina3221.cpp +2 -2
  260. esphome/components/inkplate6/display.py +12 -2
  261. esphome/components/inkplate6/inkplate.cpp +13 -9
  262. esphome/components/inkplate6/inkplate.h +7 -6
  263. esphome/components/integration/integration_sensor.cpp +1 -1
  264. esphome/components/internal_temperature/internal_temperature.cpp +4 -4
  265. esphome/components/json/json_util.cpp +4 -5
  266. esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +1 -1
  267. esphome/components/key_collector/key_collector.cpp +4 -2
  268. esphome/components/kmeteriso/kmeteriso.cpp +2 -1
  269. esphome/components/kuntze/kuntze.cpp +4 -2
  270. esphome/components/lc709203f/__init__.py +1 -0
  271. esphome/components/lc709203f/lc709203f.cpp +299 -0
  272. esphome/components/lc709203f/lc709203f.h +55 -0
  273. esphome/components/lc709203f/sensor.py +93 -0
  274. esphome/components/lcd_base/lcd_display.cpp +2 -2
  275. esphome/components/lcd_gpio/gpio_lcd_display.cpp +5 -3
  276. esphome/components/lcd_menu/lcd_menu.cpp +6 -4
  277. esphome/components/lcd_pcf8574/pcf8574_display.cpp +6 -4
  278. esphome/components/ld2410/ld2410.cpp +6 -7
  279. esphome/components/ld2420/ld2420.cpp +9 -7
  280. esphome/components/ld2450/ld2450.cpp +6 -4
  281. esphome/components/ld2450/sensor.py +2 -2
  282. esphome/components/ledc/ledc_output.cpp +32 -28
  283. esphome/components/libretiny/const.py +1 -1
  284. esphome/components/libretiny/preferences.cpp +6 -7
  285. esphome/components/light/__init__.py +2 -1
  286. esphome/components/light/esp_hsv_color.h +1 -1
  287. esphome/components/light/light_state.cpp +9 -5
  288. esphome/components/light/light_transformer.h +1 -1
  289. esphome/components/lightwaverf/LwTx.cpp +1 -1
  290. esphome/components/lightwaverf/lightwaverf.cpp +1 -1
  291. esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +1 -1
  292. esphome/components/lock/__init__.py +1 -0
  293. esphome/components/lock/lock.h +1 -1
  294. esphome/components/logger/__init__.py +6 -0
  295. esphome/components/logger/logger.cpp +9 -5
  296. esphome/components/logger/logger.h +4 -4
  297. esphome/components/logger/logger_esp32.cpp +5 -5
  298. esphome/components/ltr390/ltr390.cpp +8 -5
  299. esphome/components/ltr501/ltr501.cpp +19 -14
  300. esphome/components/ltr_als_ps/ltr_als_ps.cpp +20 -13
  301. esphome/components/lvgl/__init__.py +2 -2
  302. esphome/components/lvgl/automation.py +5 -4
  303. esphome/components/lvgl/defines.py +0 -2
  304. esphome/components/lvgl/lv_validation.py +1 -3
  305. esphome/components/lvgl/lvcode.py +7 -8
  306. esphome/components/lvgl/lvgl_esphome.cpp +26 -10
  307. esphome/components/lvgl/schemas.py +22 -23
  308. esphome/components/lvgl/trigger.py +8 -3
  309. esphome/components/lvgl/widgets/__init__.py +2 -2
  310. esphome/components/lvgl/widgets/canvas.py +9 -3
  311. esphome/components/lvgl/widgets/line.py +2 -1
  312. esphome/components/lvgl/widgets/tabview.py +7 -0
  313. esphome/components/m5stack_8angle/m5stack_8angle.cpp +3 -3
  314. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -2
  315. esphome/components/max17043/max17043.cpp +2 -2
  316. esphome/components/max31855/max31855.cpp +2 -1
  317. esphome/components/max31856/max31856.cpp +9 -11
  318. esphome/components/max31865/max31865.cpp +6 -4
  319. esphome/components/max44009/max44009.cpp +2 -2
  320. esphome/components/max6675/max6675.cpp +1 -1
  321. esphome/components/max6956/max6956.cpp +5 -3
  322. esphome/components/max7219/max7219.cpp +8 -6
  323. esphome/components/max7219digit/automation.h +52 -0
  324. esphome/components/max7219digit/display.py +93 -1
  325. esphome/components/max7219digit/max7219digit.cpp +16 -13
  326. esphome/components/max9611/max9611.cpp +9 -6
  327. esphome/components/mcp23008/mcp23008.cpp +1 -1
  328. esphome/components/mcp23016/mcp23016.cpp +1 -1
  329. esphome/components/mcp23017/mcp23017.cpp +1 -1
  330. esphome/components/mcp23s08/mcp23s08.cpp +1 -1
  331. esphome/components/mcp23s17/mcp23s17.cpp +1 -1
  332. esphome/components/mcp3008/mcp3008.cpp +1 -1
  333. esphome/components/mcp3008/sensor/mcp3008_sensor.cpp +5 -3
  334. esphome/components/mcp3204/mcp3204.cpp +1 -1
  335. esphome/components/mcp4461/mcp4461.cpp +2 -2
  336. esphome/components/mcp4725/mcp4725.cpp +2 -2
  337. esphome/components/mcp4728/mcp4728.cpp +2 -2
  338. esphome/components/mcp9600/mcp9600.cpp +1 -1
  339. esphome/components/mcp9808/mcp9808.cpp +4 -4
  340. esphome/components/mdns/mdns_component.cpp +8 -2
  341. esphome/components/mdns/mdns_component.h +3 -1
  342. esphome/components/mdns/mdns_esp32.cpp +2 -2
  343. esphome/components/media_player/__init__.py +1 -0
  344. esphome/components/micro_wake_word/micro_wake_word.cpp +1 -1
  345. esphome/components/micro_wake_word/streaming_model.cpp +10 -6
  346. esphome/components/micronova/micronova.h +2 -2
  347. esphome/components/mics_4514/mics_4514.cpp +2 -2
  348. esphome/components/midea/air_conditioner.cpp +7 -5
  349. esphome/components/midea_ir/midea_ir.cpp +1 -1
  350. esphome/components/mipi_spi/mipi_spi.cpp +24 -15
  351. esphome/components/mixer/speaker/mixer_speaker.cpp +8 -4
  352. esphome/components/mlx90393/sensor_mlx90393.cpp +2 -2
  353. esphome/components/mlx90614/mlx90614.cpp +4 -3
  354. esphome/components/mmc5603/mmc5603.cpp +2 -2
  355. esphome/components/mmc5983/mmc5983.cpp +1 -1
  356. esphome/components/modbus/modbus.cpp +7 -5
  357. esphome/components/modbus_controller/modbus_controller.cpp +6 -4
  358. esphome/components/modbus_controller/output/modbus_output.cpp +10 -6
  359. esphome/components/modbus_controller/switch/__init__.py +6 -2
  360. esphome/components/modbus_controller/switch/modbus_switch.cpp +4 -0
  361. esphome/components/modbus_controller/switch/modbus_switch.h +3 -0
  362. esphome/components/mpl3115a2/mpl3115a2.cpp +3 -2
  363. esphome/components/mpr121/mpr121.cpp +2 -2
  364. esphome/components/mpu6050/mpu6050.cpp +6 -6
  365. esphome/components/mpu6886/mpu6886.cpp +6 -6
  366. esphome/components/mqtt/mqtt_alarm_control_panel.cpp +7 -3
  367. esphome/components/mqtt/mqtt_backend_esp32.cpp +1 -1
  368. esphome/components/mqtt/mqtt_client.cpp +31 -24
  369. esphome/components/mqtt/mqtt_component.cpp +2 -2
  370. esphome/components/mqtt/mqtt_cover.cpp +8 -4
  371. esphome/components/mqtt/mqtt_fan.cpp +12 -6
  372. esphome/components/mqtt/mqtt_valve.cpp +4 -2
  373. esphome/components/ms5611/ms5611.cpp +2 -2
  374. esphome/components/ms8607/ms8607.cpp +2 -2
  375. esphome/components/msa3xx/msa3xx.cpp +16 -12
  376. esphome/components/my9231/my9231.cpp +7 -5
  377. esphome/components/nau7802/nau7802.cpp +6 -4
  378. esphome/components/neopixelbus/neopixelbus_light.h +2 -2
  379. esphome/components/network/ip_address.h +1 -1
  380. esphome/components/network/util.cpp +13 -0
  381. esphome/components/nextion/base_component.py +2 -0
  382. esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +2 -3
  383. esphome/components/nextion/display.py +34 -22
  384. esphome/components/nextion/nextion.cpp +182 -143
  385. esphome/components/nextion/nextion.h +36 -0
  386. esphome/components/nextion/nextion_commands.cpp +3 -3
  387. esphome/components/nextion/nextion_upload_arduino.cpp +58 -61
  388. esphome/components/nextion/nextion_upload_idf.cpp +69 -72
  389. esphome/components/nextion/sensor/nextion_sensor.cpp +4 -4
  390. esphome/components/nextion/switch/nextion_switch.cpp +2 -2
  391. esphome/components/nextion/text_sensor/nextion_textsensor.cpp +2 -2
  392. esphome/components/nfc/nci_message.h +1 -1
  393. esphome/components/nfc/ndef_record.h +1 -1
  394. esphome/components/nfc/ndef_record_text.h +1 -1
  395. esphome/components/nfc/ndef_record_uri.h +1 -1
  396. esphome/components/nfc/nfc.h +1 -1
  397. esphome/components/nfc/nfc_tag.h +1 -1
  398. esphome/components/noblex/noblex.cpp +1 -1
  399. esphome/components/npi19/npi19.cpp +5 -7
  400. esphome/components/number/__init__.py +11 -0
  401. esphome/components/online_image/__init__.py +13 -1
  402. esphome/components/online_image/online_image.cpp +26 -4
  403. esphome/components/online_image/online_image.h +21 -4
  404. esphome/components/opentherm/generate.py +3 -3
  405. esphome/components/opentherm/hub.cpp +11 -7
  406. esphome/components/opentherm/number/number.cpp +5 -3
  407. esphome/components/opentherm/opentherm.h +1 -1
  408. esphome/components/opentherm/schema.py +13 -13
  409. esphome/components/opentherm/validate.py +1 -1
  410. esphome/components/openthread/__init__.py +146 -0
  411. esphome/components/openthread/const.py +10 -0
  412. esphome/components/openthread/openthread.cpp +206 -0
  413. esphome/components/openthread/openthread.h +68 -0
  414. esphome/components/openthread/openthread_esp.cpp +164 -0
  415. esphome/components/openthread/tlv.py +58 -0
  416. esphome/components/openthread_info/__init__.py +0 -0
  417. esphome/components/openthread_info/openthread_info_text_sensor.cpp +24 -0
  418. esphome/components/openthread_info/openthread_info_text_sensor.h +218 -0
  419. esphome/components/openthread_info/text_sensor.py +105 -0
  420. esphome/components/output/float_output.cpp +1 -1
  421. esphome/components/output/switch/output_switch.cpp +1 -1
  422. esphome/components/packet_transport/packet_transport.cpp +6 -4
  423. esphome/components/pca6416a/pca6416a.cpp +2 -2
  424. esphome/components/pca9554/pca9554.cpp +6 -4
  425. esphome/components/pca9685/pca9685_output.cpp +12 -8
  426. esphome/components/pcd8544/pcd_8544.cpp +1 -1
  427. esphome/components/pcf85063/pcf85063.cpp +2 -2
  428. esphome/components/pcf8563/pcf8563.cpp +2 -2
  429. esphome/components/pcf8574/pcf8574.cpp +2 -2
  430. esphome/components/pid/pid_climate.cpp +8 -5
  431. esphome/components/pid/pid_climate.h +1 -1
  432. esphome/components/pid/sensor/pid_climate_sensor.cpp +1 -1
  433. esphome/components/pipsolar/output/pipsolar_output.cpp +1 -1
  434. esphome/components/pipsolar/pipsolar.cpp +3 -3
  435. esphome/components/pm1006/pm1006.cpp +3 -7
  436. esphome/components/pm1006/pm1006.h +4 -1
  437. esphome/components/pm2005/pm2005.cpp +12 -13
  438. esphome/components/pm2005/sensor.py +1 -1
  439. esphome/components/pmsa003i/pmsa003i.cpp +2 -2
  440. esphome/components/pmsx003/pmsx003.cpp +0 -4
  441. esphome/components/pmsx003/pmsx003.h +5 -2
  442. esphome/components/pmwcs3/pmwcs3.cpp +9 -13
  443. esphome/components/pmwcs3/pmwcs3.h +0 -1
  444. esphome/components/pn532/pn532.cpp +7 -7
  445. esphome/components/pn532/pn532.h +1 -1
  446. esphome/components/pn532_spi/pn532_spi.cpp +1 -1
  447. esphome/components/pn7150/pn7150.cpp +4 -4
  448. esphome/components/pn7160/pn7160.cpp +4 -4
  449. esphome/components/power_supply/power_supply.cpp +6 -4
  450. esphome/components/power_supply/power_supply.h +1 -1
  451. esphome/components/psram/__init__.py +59 -35
  452. esphome/components/pulse_counter/pulse_counter_sensor.cpp +7 -4
  453. esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +8 -5
  454. esphome/components/pylontech/pylontech.cpp +2 -2
  455. esphome/components/pylontech/sensor/pylontech_sensor.cpp +4 -2
  456. esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp +4 -2
  457. esphome/components/pzemac/pzemac.cpp +4 -2
  458. esphome/components/pzemdc/pzemdc.cpp +4 -2
  459. esphome/components/qmc5883l/qmc5883l.cpp +2 -2
  460. esphome/components/qmp6988/qmp6988.cpp +2 -2
  461. esphome/components/qmp6988/qmp6988.h +1 -1
  462. esphome/components/qr_code/qr_code.cpp +5 -3
  463. esphome/components/qspi_dbi/qspi_dbi.cpp +1 -1
  464. esphome/components/qwiic_pir/qwiic_pir.cpp +26 -28
  465. esphome/components/rc522/rc522.cpp +5 -5
  466. esphome/components/rdm6300/rdm6300.cpp +1 -0
  467. esphome/components/remote_receiver/__init__.py +10 -2
  468. esphome/components/remote_receiver/remote_receiver_esp32.cpp +22 -12
  469. esphome/components/remote_receiver/remote_receiver_esp8266.cpp +10 -7
  470. esphome/components/remote_receiver/remote_receiver_libretiny.cpp +10 -7
  471. esphome/components/remote_transmitter/__init__.py +10 -2
  472. esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +10 -6
  473. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +5 -3
  474. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +5 -3
  475. esphome/components/resistance/resistance_sensor.cpp +6 -3
  476. esphome/components/restart/button/restart_button.cpp +1 -1
  477. esphome/components/restart/switch/restart_switch.cpp +1 -1
  478. esphome/components/rotary_encoder/rotary_encoder.cpp +2 -2
  479. esphome/components/rp2040/__init__.py +7 -0
  480. esphome/components/rp2040/core.cpp +8 -1
  481. esphome/components/rp2040/gpio.cpp +26 -9
  482. esphome/components/rp2040/preferences.cpp +3 -3
  483. esphome/components/rp2040_pio_led_strip/led_strip.cpp +11 -8
  484. esphome/components/rp2040_pio_led_strip/light.py +1 -1
  485. esphome/components/rp2040_pwm/rp2040_pwm.cpp +1 -1
  486. esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +1 -1
  487. esphome/components/rtttl/rtttl.cpp +11 -9
  488. esphome/components/safe_mode/button/safe_mode_button.cpp +1 -1
  489. esphome/components/safe_mode/safe_mode.cpp +9 -8
  490. esphome/components/safe_mode/switch/safe_mode_switch.cpp +1 -1
  491. esphome/components/scd30/scd30.cpp +11 -8
  492. esphome/components/scd4x/scd4x.cpp +12 -8
  493. esphome/components/sdl/display.py +40 -0
  494. esphome/components/sdl/sdl_esphome.cpp +2 -2
  495. esphome/components/sdl/sdl_esphome.h +8 -0
  496. esphome/components/sdm_meter/sdm_meter.cpp +5 -2
  497. esphome/components/sdp3x/sdp3x.cpp +11 -11
  498. esphome/components/sds011/sds011.cpp +5 -6
  499. esphome/components/sds011/sds011.h +4 -1
  500. esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp +3 -2
  501. esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +1 -0
  502. esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +3 -2
  503. esphome/components/selec_meter/selec_meter.cpp +5 -2
  504. esphome/components/select/__init__.py +1 -0
  505. esphome/components/sen0321/sen0321.cpp +2 -2
  506. esphome/components/sen21231/sen21231.cpp +1 -1
  507. esphome/components/sen5x/sen5x.cpp +10 -7
  508. esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
  509. esphome/components/sensirion_common/i2c_sensirion.h +1 -0
  510. esphome/components/sensor/__init__.py +11 -1
  511. esphome/components/sensor/filter.h +1 -1
  512. esphome/components/sensor/sensor.h +9 -5
  513. esphome/components/servo/servo.cpp +12 -9
  514. esphome/components/servo/servo.h +1 -1
  515. esphome/components/sfa30/sfa30.cpp +1 -1
  516. esphome/components/sgp30/sgp30.cpp +10 -8
  517. esphome/components/sgp4x/sgp4x.cpp +49 -61
  518. esphome/components/sgp4x/sgp4x.h +0 -1
  519. esphome/components/shelly_dimmer/shelly_dimmer.cpp +11 -7
  520. esphome/components/sht3xd/sht3xd.cpp +1 -1
  521. esphome/components/sht4x/sht4x.cpp +2 -2
  522. esphome/components/shtcx/shtcx.cpp +13 -16
  523. esphome/components/shutdown/button/shutdown_button.cpp +1 -1
  524. esphome/components/shutdown/switch/shutdown_switch.cpp +1 -1
  525. esphome/components/sim800l/sim800l.cpp +2 -2
  526. esphome/components/slow_pwm/slow_pwm_output.cpp +4 -2
  527. esphome/components/sm16716/sm16716.cpp +1 -1
  528. esphome/components/sm2135/sm2135.cpp +1 -1
  529. esphome/components/sm2235/sm2235.cpp +5 -3
  530. esphome/components/sm2335/sm2335.cpp +5 -3
  531. esphome/components/sml/sml.cpp +1 -1
  532. esphome/components/sn74hc165/sn74hc165.cpp +1 -2
  533. esphome/components/sn74hc595/sn74hc595.cpp +1 -2
  534. esphome/components/sntp/sntp_component.cpp +1 -1
  535. esphome/components/socket/__init__.py +2 -0
  536. esphome/components/socket/bsd_sockets_impl.cpp +51 -7
  537. esphome/components/socket/lwip_raw_tcp_impl.cpp +5 -0
  538. esphome/components/socket/lwip_sockets_impl.cpp +51 -7
  539. esphome/components/socket/socket.cpp +31 -0
  540. esphome/components/socket/socket.h +27 -1
  541. esphome/components/sonoff_d1/sonoff_d1.cpp +7 -4
  542. esphome/components/sonoff_d1/sonoff_d1.h +2 -2
  543. esphome/components/sound_level/sound_level.cpp +4 -2
  544. esphome/components/speaker/media_player/__init__.py +3 -0
  545. esphome/components/speaker/media_player/speaker_media_player.cpp +1 -3
  546. esphome/components/speaker/media_player/speaker_media_player.h +6 -0
  547. esphome/components/spi/__init__.py +10 -2
  548. esphome/components/spi/spi.cpp +4 -4
  549. esphome/components/spi_device/spi_device.cpp +1 -2
  550. esphome/components/sprinkler/sprinkler.cpp +9 -6
  551. esphome/components/sps30/sps30.cpp +16 -15
  552. esphome/components/ssd1306_base/ssd1306_base.cpp +1 -1
  553. esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +11 -8
  554. esphome/components/ssd1306_spi/ssd1306_spi.cpp +10 -7
  555. esphome/components/ssd1322_base/ssd1322_base.cpp +1 -1
  556. esphome/components/ssd1322_spi/ssd1322_spi.cpp +1 -1
  557. esphome/components/ssd1325_base/ssd1325_base.cpp +1 -1
  558. esphome/components/ssd1325_spi/ssd1325_spi.cpp +1 -1
  559. esphome/components/ssd1327_base/ssd1327_base.cpp +1 -1
  560. esphome/components/ssd1327_i2c/ssd1327_i2c.cpp +2 -2
  561. esphome/components/ssd1327_spi/ssd1327_spi.cpp +1 -1
  562. esphome/components/ssd1331_base/ssd1331_base.cpp +1 -1
  563. esphome/components/ssd1331_spi/ssd1331_spi.cpp +1 -1
  564. esphome/components/ssd1351_base/ssd1351_base.cpp +1 -1
  565. esphome/components/ssd1351_spi/ssd1351_spi.cpp +1 -1
  566. esphome/components/st7567_base/st7567_base.cpp +3 -3
  567. esphome/components/st7567_i2c/st7567_i2c.cpp +7 -5
  568. esphome/components/st7567_spi/st7567_spi.cpp +1 -1
  569. esphome/components/st7701s/st7701s.cpp +4 -2
  570. esphome/components/st7735/st7735.cpp +3 -3
  571. esphome/components/st7789v/st7789v.cpp +10 -7
  572. esphome/components/st7920/st7920.cpp +6 -4
  573. esphome/components/statsd/statsd.cpp +9 -5
  574. esphome/components/status_led/light/status_led_light.cpp +1 -1
  575. esphome/components/status_led/status_led.cpp +1 -1
  576. esphome/components/stepper/stepper.h +5 -3
  577. esphome/components/sts3x/sts3x.cpp +2 -2
  578. esphome/components/switch/__init__.py +1 -0
  579. esphome/components/switch/switch.cpp +18 -12
  580. esphome/components/switch/switch.h +1 -1
  581. esphome/components/sx1509/__init__.py +53 -20
  582. esphome/components/sx1509/sx1509.cpp +29 -5
  583. esphome/components/sx1509/sx1509.h +9 -1
  584. esphome/components/t6615/t6615.cpp +1 -0
  585. esphome/components/tc74/tc74.cpp +1 -1
  586. esphome/components/tca9548a/tca9548a.cpp +1 -1
  587. esphome/components/tca9555/tca9555.cpp +2 -2
  588. esphome/components/tcs34725/tcs34725.cpp +4 -4
  589. esphome/components/tee501/tee501.cpp +3 -2
  590. esphome/components/tem3200/tem3200.cpp +5 -6
  591. esphome/components/template/alarm_control_panel/__init__.py +6 -0
  592. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +38 -12
  593. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +3 -1
  594. esphome/components/template/cover/template_cover.cpp +1 -1
  595. esphome/components/template/select/template_select.cpp +6 -4
  596. esphome/components/template/text/template_text.cpp +2 -3
  597. esphome/components/template/valve/template_valve.cpp +5 -3
  598. esphome/components/text/__init__.py +10 -11
  599. esphome/components/text_sensor/__init__.py +1 -0
  600. esphome/components/thermostat/thermostat_climate.cpp +67 -43
  601. esphome/components/time/__init__.py +1 -2
  602. esphome/components/time_based/time_based_cover.cpp +4 -2
  603. esphome/components/tlc59208f/tlc59208f_output.cpp +8 -6
  604. esphome/components/tm1621/tm1621.cpp +3 -3
  605. esphome/components/tm1637/tm1637.cpp +9 -7
  606. esphome/components/tm1638/tm1638.cpp +7 -5
  607. esphome/components/tm1651/tm1651.cpp +2 -2
  608. esphome/components/tmp102/tmp102.cpp +1 -3
  609. esphome/components/tmp102/tmp102.h +0 -3
  610. esphome/components/tmp1075/tmp1075.cpp +12 -9
  611. esphome/components/tmp117/tmp117.cpp +2 -2
  612. esphome/components/tof10120/tof10120_sensor.cpp +2 -2
  613. esphome/components/tormatic/tormatic_cover.cpp +4 -2
  614. esphome/components/tsl2561/tsl2561.cpp +7 -5
  615. esphome/components/tsl2591/tsl2591.cpp +25 -27
  616. esphome/components/tt21100/touchscreen/tt21100.cpp +1 -1
  617. esphome/components/ttp229_bsf/ttp229_bsf.cpp +1 -1
  618. esphome/components/ttp229_lsf/ttp229_lsf.cpp +2 -2
  619. esphome/components/tuya/select/tuya_select.cpp +5 -3
  620. esphome/components/tx20/tx20.cpp +3 -3
  621. esphome/components/uart/__init__.py +4 -5
  622. esphome/components/uart/button/uart_button.cpp +1 -1
  623. esphome/components/uart/switch/uart_switch.cpp +2 -2
  624. esphome/components/uart/uart.cpp +2 -2
  625. esphome/components/uart/uart_component_esp32_arduino.cpp +8 -6
  626. esphome/components/uart/uart_component_esp8266.cpp +9 -7
  627. esphome/components/uart/uart_component_esp_idf.cpp +9 -7
  628. esphome/components/uart/uart_component_host.cpp +11 -8
  629. esphome/components/uart/uart_component_libretiny.cpp +8 -6
  630. esphome/components/uart/uart_component_rp2040.cpp +8 -6
  631. esphome/components/udp/udp_component.cpp +9 -5
  632. esphome/components/ufire_ec/ufire_ec.cpp +5 -3
  633. esphome/components/ufire_ise/ufire_ise.cpp +1 -1
  634. esphome/components/ultrasonic/ultrasonic_sensor.cpp +5 -3
  635. esphome/components/update/__init__.py +1 -0
  636. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
  637. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +4 -2
  638. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  639. esphome/components/usb_host/__init__.py +64 -0
  640. esphome/components/usb_host/usb_host.h +116 -0
  641. esphome/components/usb_host/usb_host_client.cpp +394 -0
  642. esphome/components/usb_host/usb_host_component.cpp +35 -0
  643. esphome/components/usb_uart/__init__.py +134 -0
  644. esphome/components/usb_uart/ch34x.cpp +80 -0
  645. esphome/components/usb_uart/cp210x.cpp +126 -0
  646. esphome/components/usb_uart/usb_uart.cpp +328 -0
  647. esphome/components/usb_uart/usb_uart.h +151 -0
  648. esphome/components/valve/__init__.py +1 -0
  649. esphome/components/veml3235/veml3235.cpp +13 -9
  650. esphome/components/veml7700/veml7700.cpp +10 -6
  651. esphome/components/voice_assistant/voice_assistant.cpp +7 -7
  652. esphome/components/wake_on_lan/wake_on_lan.cpp +1 -1
  653. esphome/components/waveshare_epaper/waveshare_epaper.cpp +1 -1
  654. esphome/components/web_server/server_index_v2.h +632 -630
  655. esphome/components/web_server/server_index_v3.h +411 -409
  656. esphome/components/web_server/web_server.cpp +5 -3
  657. esphome/components/web_server_base/web_server_base.cpp +1 -1
  658. esphome/components/web_server_idf/utils.cpp +1 -1
  659. esphome/components/web_server_idf/web_server_idf.cpp +1 -1
  660. esphome/components/weikai/__init__.py +2 -0
  661. esphome/components/weikai/weikai.cpp +23 -21
  662. esphome/components/weikai_i2c/weikai_i2c.cpp +14 -9
  663. esphome/components/weikai_spi/weikai_spi.cpp +11 -6
  664. esphome/components/wiegand/wiegand.cpp +1 -1
  665. esphome/components/wifi/wifi_component.cpp +50 -37
  666. esphome/components/wifi/wifi_component.h +7 -4
  667. esphome/components/wifi/wifi_component_esp32_arduino.cpp +2 -2
  668. esphome/components/wifi/wifi_component_esp8266.cpp +3 -3
  669. esphome/components/wifi/wifi_component_libretiny.cpp +4 -4
  670. esphome/components/wireguard/wireguard.cpp +21 -21
  671. esphome/components/wl_134/text_sensor.py +1 -2
  672. esphome/components/wled/wled_light_effect.cpp +1 -1
  673. esphome/components/x9c/x9c.cpp +5 -3
  674. esphome/components/xgzp68xx/xgzp68xx.cpp +8 -6
  675. esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +4 -2
  676. esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +4 -2
  677. esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +4 -2
  678. esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp +1 -1
  679. esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +4 -2
  680. esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +4 -2
  681. esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +4 -2
  682. esphome/components/xl9535/xl9535.cpp +2 -2
  683. esphome/components/xpt2046/touchscreen/xpt2046.cpp +12 -11
  684. esphome/components/xpt2046/touchscreen/xpt2046.h +2 -2
  685. esphome/config.py +3 -2
  686. esphome/config_validation.py +46 -17
  687. esphome/const.py +10 -1
  688. esphome/core/__init__.py +37 -18
  689. esphome/core/application.cpp +188 -5
  690. esphome/core/application.h +110 -0
  691. esphome/core/component.h +13 -0
  692. esphome/core/config.py +12 -0
  693. esphome/core/defines.h +10 -2
  694. esphome/core/hal.h +5 -0
  695. esphome/core/helpers.cpp +1 -1
  696. esphome/core/helpers.h +4 -4
  697. esphome/core/log.h +2 -0
  698. esphome/core/log_const_en.h +4 -0
  699. esphome/core/scheduler.cpp +2 -2
  700. esphome/coroutine.py +3 -4
  701. esphome/cpp_generator.py +32 -32
  702. esphome/dashboard/core.py +2 -2
  703. esphome/dashboard/web_server.py +2 -2
  704. esphome/git.py +4 -4
  705. esphome/helpers.py +5 -6
  706. esphome/loader.py +8 -7
  707. esphome/log.py +7 -1
  708. esphome/platformio_api.py +2 -3
  709. esphome/storage_json.py +13 -5
  710. esphome/types.py +12 -13
  711. esphome/util.py +1 -2
  712. esphome/writer.py +5 -3
  713. esphome/yaml_util.py +6 -1
  714. esphome/zeroconf.py +1 -1
  715. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/METADATA +12 -11
  716. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/RECORD +720 -667
  717. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/WHEEL +1 -1
  718. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/entry_points.txt +0 -0
  719. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/licenses/LICENSE +0 -0
  720. {esphome-2025.5.1.dist-info → esphome-2025.6.0b1.dist-info}/top_level.txt +0 -0
@@ -1,26 +1,19 @@
1
1
  #include "api_frame_helper.h"
2
2
  #ifdef USE_API
3
- #include "esphome/core/log.h"
3
+ #include "esphome/core/application.h"
4
4
  #include "esphome/core/hal.h"
5
5
  #include "esphome/core/helpers.h"
6
- #include "esphome/core/application.h"
6
+ #include "esphome/core/log.h"
7
7
  #include "proto.h"
8
8
  #include "api_pb2_size.h"
9
9
  #include <cstring>
10
+ #include <cinttypes>
10
11
 
11
12
  namespace esphome {
12
13
  namespace api {
13
14
 
14
15
  static const char *const TAG = "api.socket";
15
16
 
16
- /// Is the given return value (from write syscalls) a wouldblock error?
17
- bool is_would_block(ssize_t ret) {
18
- if (ret == -1) {
19
- return errno == EWOULDBLOCK || errno == EAGAIN;
20
- }
21
- return ret == 0;
22
- }
23
-
24
17
  const char *api_error_to_str(APIError err) {
25
18
  // not using switch to ensure compiler doesn't try to build a big table out of it
26
19
  if (err == APIError::OK) {
@@ -73,92 +66,154 @@ const char *api_error_to_str(APIError err) {
73
66
  return "UNKNOWN";
74
67
  }
75
68
 
76
- // Common implementation for writing raw data to socket
77
- template<typename StateEnum>
78
- APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
79
- std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
80
- StateEnum failed_state) {
81
- // This method writes data to socket or buffers it
69
+ // Helper method to buffer data from IOVs
70
+ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
71
+ SendBuffer buffer;
72
+ buffer.data.reserve(total_write_len);
73
+ for (int i = 0; i < iovcnt; i++) {
74
+ const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base);
75
+ buffer.data.insert(buffer.data.end(), data, data + iov[i].iov_len);
76
+ }
77
+ this->tx_buf_.push_back(std::move(buffer));
78
+ }
79
+
80
+ // This method writes data to socket or buffers it
81
+ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
82
82
  // Returns APIError::OK if successful (or would block, but data has been buffered)
83
- // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
83
+ // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
84
84
 
85
85
  if (iovcnt == 0)
86
86
  return APIError::OK; // Nothing to do, success
87
87
 
88
- size_t total_write_len = 0;
88
+ uint16_t total_write_len = 0;
89
89
  for (int i = 0; i < iovcnt; i++) {
90
90
  #ifdef HELPER_LOG_PACKETS
91
91
  ESP_LOGVV(TAG, "Sending raw: %s",
92
92
  format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
93
93
  #endif
94
- total_write_len += iov[i].iov_len;
94
+ total_write_len += static_cast<uint16_t>(iov[i].iov_len);
95
95
  }
96
96
 
97
- if (!tx_buf.empty()) {
98
- // try to empty tx_buf first
99
- while (!tx_buf.empty()) {
100
- ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
101
- if (is_would_block(sent)) {
102
- break;
103
- } else if (sent == -1) {
104
- ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
105
- state = failed_state;
106
- return APIError::SOCKET_WRITE_FAILED; // Socket write failed
107
- }
108
- // TODO: inefficient if multiple packets in txbuf
109
- // replace with deque of buffers
110
- tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
97
+ // Try to send any existing buffered data first if there is any
98
+ if (!this->tx_buf_.empty()) {
99
+ APIError send_result = try_send_tx_buf_();
100
+ // If real error occurred (not just WOULD_BLOCK), return it
101
+ if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
102
+ return send_result;
111
103
  }
112
- }
113
104
 
114
- if (!tx_buf.empty()) {
115
- // tx buf not empty, can't write now because then stream would be inconsistent
116
- // Reserve space upfront to avoid multiple reallocations
117
- tx_buf.reserve(tx_buf.size() + total_write_len);
118
- for (int i = 0; i < iovcnt; i++) {
119
- tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
120
- reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
105
+ // If there is still data in the buffer, we can't send, buffer
106
+ // the new data and return
107
+ if (!this->tx_buf_.empty()) {
108
+ this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
109
+ return APIError::OK; // Success, data buffered
121
110
  }
122
- return APIError::OK; // Success, data buffered
123
111
  }
124
112
 
125
- ssize_t sent = socket->writev(iov, iovcnt);
126
- if (is_would_block(sent)) {
127
- // operation would block, add buffer to tx_buf
128
- // Reserve space upfront to avoid multiple reallocations
129
- tx_buf.reserve(tx_buf.size() + total_write_len);
130
- for (int i = 0; i < iovcnt; i++) {
131
- tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
132
- reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
113
+ // Try to send directly if no buffered data
114
+ ssize_t sent = this->socket_->writev(iov, iovcnt);
115
+
116
+ if (sent == -1) {
117
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
118
+ // Socket would block, buffer the data
119
+ this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
120
+ return APIError::OK; // Success, data buffered
133
121
  }
134
- return APIError::OK; // Success, data buffered
135
- } else if (sent == -1) {
136
- // an error occurred
137
- ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
138
- state = failed_state;
122
+ // Socket error
123
+ ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
124
+ this->state_ = State::FAILED;
139
125
  return APIError::SOCKET_WRITE_FAILED; // Socket write failed
140
- } else if ((size_t) sent != total_write_len) {
141
- // partially sent, add end to tx_buf
142
- size_t remaining = total_write_len - sent;
143
- // Reserve space upfront to avoid multiple reallocations
144
- tx_buf.reserve(tx_buf.size() + remaining);
126
+ } else if (static_cast<uint16_t>(sent) < total_write_len) {
127
+ // Partially sent, buffer the remaining data
128
+ SendBuffer buffer;
129
+ uint16_t to_consume = static_cast<uint16_t>(sent);
130
+ uint16_t remaining = total_write_len - static_cast<uint16_t>(sent);
131
+
132
+ buffer.data.reserve(remaining);
145
133
 
146
- size_t to_consume = sent;
147
134
  for (int i = 0; i < iovcnt; i++) {
148
135
  if (to_consume >= iov[i].iov_len) {
149
- to_consume -= iov[i].iov_len;
136
+ // This segment was fully sent
137
+ to_consume -= static_cast<uint16_t>(iov[i].iov_len);
150
138
  } else {
151
- tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
152
- reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
139
+ // This segment was partially sent or not sent at all
140
+ const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume;
141
+ uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_consume;
142
+ buffer.data.insert(buffer.data.end(), data, data + len);
153
143
  to_consume = 0;
154
144
  }
155
145
  }
156
- return APIError::OK; // Success, data buffered
146
+
147
+ this->tx_buf_.push_back(std::move(buffer));
157
148
  }
158
- return APIError::OK; // Success, all data sent
149
+
150
+ return APIError::OK; // Success, all data sent or buffered
159
151
  }
160
152
 
161
- #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
153
+ // Common implementation for trying to send buffered data
154
+ // IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
155
+ APIError APIFrameHelper::try_send_tx_buf_() {
156
+ // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
157
+ bool tx_buf_empty = false;
158
+ while (!tx_buf_empty) {
159
+ // Get the first buffer in the queue
160
+ SendBuffer &front_buffer = this->tx_buf_.front();
161
+
162
+ // Try to send the remaining data in this buffer
163
+ ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
164
+
165
+ if (sent == -1) {
166
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
167
+ // Real socket error (not just would block)
168
+ ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
169
+ this->state_ = State::FAILED;
170
+ return APIError::SOCKET_WRITE_FAILED; // Socket write failed
171
+ }
172
+ // Socket would block, we'll try again later
173
+ return APIError::WOULD_BLOCK;
174
+ } else if (sent == 0) {
175
+ // Nothing sent but not an error
176
+ return APIError::WOULD_BLOCK;
177
+ } else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
178
+ // Partially sent, update offset
179
+ // Cast to ensure no overflow issues with uint16_t
180
+ front_buffer.offset += static_cast<uint16_t>(sent);
181
+ return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
182
+ } else {
183
+ // Buffer completely sent, remove it from the queue
184
+ this->tx_buf_.pop_front();
185
+ // Update empty status for the loop condition
186
+ tx_buf_empty = this->tx_buf_.empty();
187
+ // Continue loop to try sending the next buffer
188
+ }
189
+ }
190
+
191
+ return APIError::OK; // All buffers sent successfully
192
+ }
193
+
194
+ APIError APIFrameHelper::init_common_() {
195
+ if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
196
+ ESP_LOGVV(TAG, "%s: Bad state for init %d", this->info_.c_str(), (int) state_);
197
+ return APIError::BAD_STATE;
198
+ }
199
+ int err = this->socket_->setblocking(false);
200
+ if (err != 0) {
201
+ state_ = State::FAILED;
202
+ ESP_LOGVV(TAG, "%s: Setting nonblocking failed with errno %d", this->info_.c_str(), errno);
203
+ return APIError::TCP_NONBLOCKING_FAILED;
204
+ }
205
+
206
+ int enable = 1;
207
+ err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
208
+ if (err != 0) {
209
+ state_ = State::FAILED;
210
+ ESP_LOGVV(TAG, "%s: Setting nodelay failed with errno %d", this->info_.c_str(), errno);
211
+ return APIError::TCP_NODELAY_FAILED;
212
+ }
213
+ return APIError::OK;
214
+ }
215
+
216
+ #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
162
217
  // uncomment to log raw packets
163
218
  //#define HELPER_LOG_PACKETS
164
219
 
@@ -206,23 +261,9 @@ std::string noise_err_to_str(int err) {
206
261
 
207
262
  /// Initialize the frame helper, returns OK if successful.
208
263
  APIError APINoiseFrameHelper::init() {
209
- if (state_ != State::INITIALIZE || socket_ == nullptr) {
210
- HELPER_LOG("Bad state for init %d", (int) state_);
211
- return APIError::BAD_STATE;
212
- }
213
- int err = socket_->setblocking(false);
214
- if (err != 0) {
215
- state_ = State::FAILED;
216
- HELPER_LOG("Setting nonblocking failed with errno %d", errno);
217
- return APIError::TCP_NONBLOCKING_FAILED;
218
- }
219
-
220
- int enable = 1;
221
- err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
222
- if (err != 0) {
223
- state_ = State::FAILED;
224
- HELPER_LOG("Setting nodelay failed with errno %d", errno);
225
- return APIError::TCP_NODELAY_FAILED;
264
+ APIError err = init_common_();
265
+ if (err != APIError::OK) {
266
+ return err;
226
267
  }
227
268
 
228
269
  // init prologue
@@ -234,17 +275,16 @@ APIError APINoiseFrameHelper::init() {
234
275
  /// Run through handshake messages (if in that phase)
235
276
  APIError APINoiseFrameHelper::loop() {
236
277
  APIError err = state_action_();
237
- if (err == APIError::WOULD_BLOCK)
238
- return APIError::OK;
239
- if (err != APIError::OK)
278
+ if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
240
279
  return err;
241
- if (!tx_buf_.empty()) {
280
+ }
281
+ if (!this->tx_buf_.empty()) {
242
282
  err = try_send_tx_buf_();
243
- if (err != APIError::OK) {
283
+ if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
244
284
  return err;
245
285
  }
246
286
  }
247
- return APIError::OK;
287
+ return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
248
288
  }
249
289
 
250
290
  /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -270,8 +310,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
270
310
  // read header
271
311
  if (rx_header_buf_len_ < 3) {
272
312
  // no header information yet
273
- size_t to_read = 3 - rx_header_buf_len_;
274
- ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
313
+ uint8_t to_read = 3 - rx_header_buf_len_;
314
+ ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
275
315
  if (received == -1) {
276
316
  if (errno == EWOULDBLOCK || errno == EAGAIN) {
277
317
  return APIError::WOULD_BLOCK;
@@ -284,8 +324,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
284
324
  HELPER_LOG("Connection closed");
285
325
  return APIError::CONNECTION_CLOSED;
286
326
  }
287
- rx_header_buf_len_ += received;
288
- if ((size_t) received != to_read) {
327
+ rx_header_buf_len_ += static_cast<uint8_t>(received);
328
+ if (static_cast<uint8_t>(received) != to_read) {
289
329
  // not a full read
290
330
  return APIError::WOULD_BLOCK;
291
331
  }
@@ -317,8 +357,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
317
357
 
318
358
  if (rx_buf_len_ < msg_size) {
319
359
  // more data to read
320
- size_t to_read = msg_size - rx_buf_len_;
321
- ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
360
+ uint16_t to_read = msg_size - rx_buf_len_;
361
+ ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
322
362
  if (received == -1) {
323
363
  if (errno == EWOULDBLOCK || errno == EAGAIN) {
324
364
  return APIError::WOULD_BLOCK;
@@ -331,8 +371,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
331
371
  HELPER_LOG("Connection closed");
332
372
  return APIError::CONNECTION_CLOSED;
333
373
  }
334
- rx_buf_len_ += received;
335
- if ((size_t) received != to_read) {
374
+ rx_buf_len_ += static_cast<uint16_t>(received);
375
+ if (static_cast<uint16_t>(received) != to_read) {
336
376
  // not all read
337
377
  return APIError::WOULD_BLOCK;
338
378
  }
@@ -381,6 +421,8 @@ APIError APINoiseFrameHelper::state_action_() {
381
421
  if (aerr != APIError::OK)
382
422
  return aerr;
383
423
  // ignore contents, may be used in future for flags
424
+ // Reserve space for: existing prologue + 2 size bytes + frame data
425
+ prologue_.reserve(prologue_.size() + 2 + frame.msg.size());
384
426
  prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
385
427
  prologue_.push_back((uint8_t) frame.msg.size());
386
428
  prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
@@ -389,16 +431,20 @@ APIError APINoiseFrameHelper::state_action_() {
389
431
  }
390
432
  if (state_ == State::SERVER_HELLO) {
391
433
  // send server hello
434
+ const std::string &name = App.get_name();
435
+ const std::string &mac = get_mac_address();
436
+
392
437
  std::vector<uint8_t> msg;
438
+ // Reserve space for: 1 byte proto + name + null + mac + null
439
+ msg.reserve(1 + name.size() + 1 + mac.size() + 1);
440
+
393
441
  // chosen proto
394
442
  msg.push_back(0x01);
395
443
 
396
444
  // node name, terminated by null byte
397
- const std::string &name = App.get_name();
398
445
  const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
399
446
  msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
400
447
  // node mac, terminated by null byte
401
- const std::string &mac = get_mac_address();
402
448
  const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
403
449
  msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
404
450
 
@@ -505,7 +551,6 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
505
551
  write_frame_(data.data(), data.size());
506
552
  state_ = orig_state;
507
553
  }
508
-
509
554
  APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
510
555
  int err;
511
556
  APIError aerr;
@@ -533,7 +578,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
533
578
  return APIError::CIPHERSTATE_DECRYPT_FAILED;
534
579
  }
535
580
 
536
- size_t msg_size = mbuf.size;
581
+ uint16_t msg_size = mbuf.size;
537
582
  uint8_t *msg_data = frame.msg.data();
538
583
  if (msg_size < 4) {
539
584
  state_ = State::FAILED;
@@ -559,11 +604,22 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
559
604
  buffer->type = type;
560
605
  return APIError::OK;
561
606
  }
562
- bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
563
607
  APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
564
- int err;
565
- APIError aerr;
566
- aerr = state_action_();
608
+ std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
609
+ uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
610
+
611
+ // Resize to include MAC space (required for Noise encryption)
612
+ raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
613
+
614
+ // Use write_protobuf_packets with a single packet
615
+ std::vector<PacketInfo> packets;
616
+ packets.emplace_back(type, 0, payload_len);
617
+
618
+ return write_protobuf_packets(buffer, packets);
619
+ }
620
+
621
+ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) {
622
+ APIError aerr = state_action_();
567
623
  if (aerr != APIError::OK) {
568
624
  return aerr;
569
625
  }
@@ -572,77 +628,67 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
572
628
  return APIError::WOULD_BLOCK;
573
629
  }
574
630
 
575
- std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
576
- // Message data starts after padding
577
- size_t payload_len = raw_buffer->size() - frame_header_padding_;
578
- size_t padding = 0;
579
- size_t msg_len = 4 + payload_len + padding;
580
-
581
- // We need to resize to include MAC space, but we already reserved it in create_buffer
582
- raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
583
-
584
- // Write the noise header in the padded area
585
- // Buffer layout:
586
- // [0] - 0x01 indicator byte
587
- // [1-2] - Size of encrypted payload (filled after encryption)
588
- // [3-4] - Message type (encrypted)
589
- // [5-6] - Payload length (encrypted)
590
- // [7...] - Actual payload data (encrypted)
591
- uint8_t *buf_start = raw_buffer->data();
592
- buf_start[0] = 0x01; // indicator
593
- // buf_start[1], buf_start[2] to be set later after encryption
594
- const uint8_t msg_offset = 3;
595
- buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
596
- buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
597
- buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
598
- buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
599
- // payload data is already in the buffer starting at position 7
600
-
601
- NoiseBuffer mbuf;
602
- noise_buffer_init(mbuf);
603
- // The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
604
- noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
605
- err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
606
- if (err != 0) {
607
- state_ = State::FAILED;
608
- HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
609
- return APIError::CIPHERSTATE_ENCRYPT_FAILED;
631
+ if (packets.empty()) {
632
+ return APIError::OK;
610
633
  }
611
634
 
612
- size_t total_len = 3 + mbuf.size;
613
- buf_start[1] = (uint8_t) (mbuf.size >> 8);
614
- buf_start[2] = (uint8_t) mbuf.size;
615
-
616
- struct iovec iov;
617
- // Point iov_base to the beginning of the buffer (no unused padding in Noise)
618
- // We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
619
- iov.iov_base = buf_start;
620
- iov.iov_len = total_len;
621
-
622
- // write raw to not have two packets sent if NAGLE disabled
623
- return write_raw_(&iov, 1);
624
- }
625
- APIError APINoiseFrameHelper::try_send_tx_buf_() {
626
- // try send from tx_buf
627
- while (state_ != State::CLOSED && !tx_buf_.empty()) {
628
- ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
629
- if (sent == -1) {
630
- if (errno == EWOULDBLOCK || errno == EAGAIN)
631
- break;
635
+ std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
636
+ this->reusable_iovs_.clear();
637
+ this->reusable_iovs_.reserve(packets.size());
638
+
639
+ // We need to encrypt each packet in place
640
+ for (const auto &packet : packets) {
641
+ uint16_t type = packet.message_type;
642
+ uint16_t offset = packet.offset;
643
+ uint16_t payload_len = packet.payload_size;
644
+ uint16_t msg_len = 4 + payload_len; // type(2) + data_len(2) + payload
645
+
646
+ // The buffer already has padding at offset
647
+ uint8_t *buf_start = raw_buffer->data() + offset;
648
+
649
+ // Write noise header
650
+ buf_start[0] = 0x01; // indicator
651
+ // buf_start[1], buf_start[2] to be set after encryption
652
+
653
+ // Write message header (to be encrypted)
654
+ const uint8_t msg_offset = 3;
655
+ buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
656
+ buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
657
+ buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
658
+ buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
659
+ // payload data is already in the buffer starting at offset + 7
660
+
661
+ // Make sure we have space for MAC
662
+ // The buffer should already have been sized appropriately
663
+
664
+ // Encrypt the message in place
665
+ NoiseBuffer mbuf;
666
+ noise_buffer_init(mbuf);
667
+ noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
668
+
669
+ int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
670
+ if (err != 0) {
632
671
  state_ = State::FAILED;
633
- HELPER_LOG("Socket write failed with errno %d", errno);
634
- return APIError::SOCKET_WRITE_FAILED;
635
- } else if (sent == 0) {
636
- break;
672
+ HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
673
+ return APIError::CIPHERSTATE_ENCRYPT_FAILED;
637
674
  }
638
- // TODO: inefficient if multiple packets in txbuf
639
- // replace with deque of buffers
640
- tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
675
+
676
+ // Fill in the encrypted size
677
+ buf_start[1] = (uint8_t) (mbuf.size >> 8);
678
+ buf_start[2] = (uint8_t) mbuf.size;
679
+
680
+ // Add iovec for this encrypted packet
681
+ struct iovec iov;
682
+ iov.iov_base = buf_start;
683
+ iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
684
+ this->reusable_iovs_.push_back(iov);
641
685
  }
642
686
 
643
- return APIError::OK;
687
+ // Send all encrypted packets in one writev call
688
+ return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
644
689
  }
645
- APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
690
+
691
+ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
646
692
  uint8_t header[3];
647
693
  header[0] = 0x01; // indicator
648
694
  header[1] = (uint8_t) (len >> 8);
@@ -652,12 +698,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
652
698
  iov[0].iov_base = header;
653
699
  iov[0].iov_len = 3;
654
700
  if (len == 0) {
655
- return write_raw_(iov, 1);
701
+ return this->write_raw_(iov, 1);
656
702
  }
657
703
  iov[1].iov_base = const_cast<uint8_t *>(data);
658
704
  iov[1].iov_len = len;
659
705
 
660
- return write_raw_(iov, 2);
706
+ return this->write_raw_(iov, 2);
661
707
  }
662
708
 
663
709
  /** Initiate the data structures for the handshake.
@@ -752,58 +798,25 @@ APINoiseFrameHelper::~APINoiseFrameHelper() {
752
798
  }
753
799
  }
754
800
 
755
- APIError APINoiseFrameHelper::close() {
756
- state_ = State::CLOSED;
757
- int err = socket_->close();
758
- if (err == -1)
759
- return APIError::CLOSE_FAILED;
760
- return APIError::OK;
761
- }
762
- APIError APINoiseFrameHelper::shutdown(int how) {
763
- int err = socket_->shutdown(how);
764
- if (err == -1)
765
- return APIError::SHUTDOWN_FAILED;
766
- if (how == SHUT_RDWR) {
767
- state_ = State::CLOSED;
768
- }
769
- return APIError::OK;
770
- }
771
801
  extern "C" {
772
802
  // declare how noise generates random bytes (here with a good HWRNG based on the RF system)
773
803
  void noise_rand_bytes(void *output, size_t len) {
774
804
  if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
775
- ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
805
+ ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
776
806
  arch_restart();
777
807
  }
778
808
  }
779
809
  }
780
810
 
781
- // Explicit template instantiation for Noise
782
- template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
783
- const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
784
- APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
785
811
  #endif // USE_API_NOISE
786
812
 
787
813
  #ifdef USE_API_PLAINTEXT
788
814
 
789
815
  /// Initialize the frame helper, returns OK if successful.
790
816
  APIError APIPlaintextFrameHelper::init() {
791
- if (state_ != State::INITIALIZE || socket_ == nullptr) {
792
- HELPER_LOG("Bad state for init %d", (int) state_);
793
- return APIError::BAD_STATE;
794
- }
795
- int err = socket_->setblocking(false);
796
- if (err != 0) {
797
- state_ = State::FAILED;
798
- HELPER_LOG("Setting nonblocking failed with errno %d", errno);
799
- return APIError::TCP_NONBLOCKING_FAILED;
800
- }
801
- int enable = 1;
802
- err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
803
- if (err != 0) {
804
- state_ = State::FAILED;
805
- HELPER_LOG("Setting nodelay failed with errno %d", errno);
806
- return APIError::TCP_NODELAY_FAILED;
817
+ APIError err = init_common_();
818
+ if (err != APIError::OK) {
819
+ return err;
807
820
  }
808
821
 
809
822
  state_ = State::DATA;
@@ -814,14 +827,13 @@ APIError APIPlaintextFrameHelper::loop() {
814
827
  if (state_ != State::DATA) {
815
828
  return APIError::BAD_STATE;
816
829
  }
817
- // try send pending TX data
818
- if (!tx_buf_.empty()) {
830
+ if (!this->tx_buf_.empty()) {
819
831
  APIError err = try_send_tx_buf_();
820
- if (err != APIError::OK) {
832
+ if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
821
833
  return err;
822
834
  }
823
835
  }
824
- return APIError::OK;
836
+ return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
825
837
  }
826
838
 
827
839
  /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -841,12 +853,15 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
841
853
 
842
854
  // read header
843
855
  while (!rx_header_parsed_) {
844
- uint8_t data;
845
- // Reading one byte at a time is fastest in practice for ESP32 when
846
- // there is no data on the wire (which is the common case).
847
- // This results in faster failure detection compared to
848
- // attempting to read multiple bytes at once.
849
- ssize_t received = socket_->read(&data, 1);
856
+ // Now that we know when the socket is ready, we can read up to 3 bytes
857
+ // into the rx_header_buf_ before we have to switch back to reading
858
+ // one byte at a time to ensure we don't read past the message and
859
+ // into the next one.
860
+
861
+ // Read directly into rx_header_buf_ at the current position
862
+ // Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
863
+ ssize_t received =
864
+ this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
850
865
  if (received == -1) {
851
866
  if (errno == EWOULDBLOCK || errno == EAGAIN) {
852
867
  return APIError::WOULD_BLOCK;
@@ -860,64 +875,74 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
860
875
  return APIError::CONNECTION_CLOSED;
861
876
  }
862
877
 
863
- // Successfully read a byte
864
-
865
- // Process byte according to current buffer position
866
- if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
867
- if (data != 0x00) {
878
+ // If this was the first read, validate the indicator byte
879
+ if (rx_header_buf_pos_ == 0 && received > 0) {
880
+ if (rx_header_buf_[0] != 0x00) {
868
881
  state_ = State::FAILED;
869
- HELPER_LOG("Bad indicator byte %u", data);
882
+ HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
870
883
  return APIError::BAD_INDICATOR;
871
884
  }
872
- // We don't store the indicator byte, just increment position
873
- rx_header_buf_pos_ = 1; // Set to 1 directly
874
- continue; // Need more bytes before we can parse
875
885
  }
876
886
 
877
- // Check buffer overflow before storing
878
- if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
887
+ rx_header_buf_pos_ += received;
888
+
889
+ // Check for buffer overflow
890
+ if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
879
891
  state_ = State::FAILED;
880
892
  HELPER_LOG("Header buffer overflow");
881
893
  return APIError::BAD_DATA_PACKET;
882
894
  }
883
895
 
884
- // Store byte in buffer (adjust index to account for skipped indicator byte)
885
- rx_header_buf_[rx_header_buf_pos_ - 1] = data;
886
-
887
- // Increment position after storing
888
- rx_header_buf_pos_++;
889
-
890
- // Case 3: If we only have one varint byte, we need more
891
- if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
892
- continue; // Need more bytes before we can parse
896
+ // Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse
897
+ if (rx_header_buf_pos_ < 3) {
898
+ continue;
893
899
  }
894
900
 
895
901
  // At this point, we have at least 3 bytes total:
896
- // - Validated indicator byte (0x00) but not stored
902
+ // - Validated indicator byte (0x00) stored at position 0
897
903
  // - At least 2 bytes in the buffer for the varints
898
904
  // Buffer layout:
899
- // First 1-3 bytes: Message size varint (variable length)
900
- // - 2 bytes would only allow up to 16383, which is less than noise's 65535
905
+ // [0]: indicator byte (0x00)
906
+ // [1-3]: Message size varint (variable length)
907
+ // - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
901
908
  // - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
902
- // Remaining 1-2 bytes: Message type varint (variable length)
909
+ // [2-5]: Message type varint (variable length)
903
910
  // We now attempt to parse both varints. If either is incomplete,
904
911
  // we'll continue reading more bytes.
905
912
 
913
+ // Skip indicator byte at position 0
914
+ uint8_t varint_pos = 1;
906
915
  uint32_t consumed = 0;
907
- auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
916
+
917
+ auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
908
918
  if (!msg_size_varint.has_value()) {
909
919
  // not enough data there yet
910
920
  continue;
911
921
  }
912
922
 
913
- rx_header_parsed_len_ = msg_size_varint->as_uint32();
923
+ if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
924
+ state_ = State::FAILED;
925
+ HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
926
+ std::numeric_limits<uint16_t>::max());
927
+ return APIError::BAD_DATA_PACKET;
928
+ }
929
+ rx_header_parsed_len_ = msg_size_varint->as_uint16();
930
+
931
+ // Move to next varint position
932
+ varint_pos += consumed;
914
933
 
915
- auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
934
+ auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
916
935
  if (!msg_type_varint.has_value()) {
917
936
  // not enough data there yet
918
937
  continue;
919
938
  }
920
- rx_header_parsed_type_ = msg_type_varint->as_uint32();
939
+ if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
940
+ state_ = State::FAILED;
941
+ HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
942
+ std::numeric_limits<uint16_t>::max());
943
+ return APIError::BAD_DATA_PACKET;
944
+ }
945
+ rx_header_parsed_type_ = msg_type_varint->as_uint16();
921
946
  rx_header_parsed_ = true;
922
947
  }
923
948
  // header reading done
@@ -929,8 +954,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
929
954
 
930
955
  if (rx_buf_len_ < rx_header_parsed_len_) {
931
956
  // more data to read
932
- size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
933
- ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
957
+ uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
958
+ ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
934
959
  if (received == -1) {
935
960
  if (errno == EWOULDBLOCK || errno == EAGAIN) {
936
961
  return APIError::WOULD_BLOCK;
@@ -943,8 +968,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
943
968
  HELPER_LOG("Connection closed");
944
969
  return APIError::CONNECTION_CLOSED;
945
970
  }
946
- rx_buf_len_ += received;
947
- if ((size_t) received != to_read) {
971
+ rx_buf_len_ += static_cast<uint16_t>(received);
972
+ if (static_cast<uint16_t>(received) != to_read) {
948
973
  // not all read
949
974
  return APIError::WOULD_BLOCK;
950
975
  }
@@ -962,7 +987,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
962
987
  rx_header_parsed_ = false;
963
988
  return APIError::OK;
964
989
  }
965
-
966
990
  APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
967
991
  APIError aerr;
968
992
 
@@ -990,7 +1014,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
990
1014
  "Bad indicator byte";
991
1015
  iov[0].iov_base = (void *) msg;
992
1016
  iov[0].iov_len = 19;
993
- write_raw_(iov, 1);
1017
+ this->write_raw_(iov, 1);
994
1018
  }
995
1019
  return aerr;
996
1020
  }
@@ -1001,108 +1025,89 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
1001
1025
  buffer->type = rx_header_parsed_type_;
1002
1026
  return APIError::OK;
1003
1027
  }
1004
- bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
1005
1028
  APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
1006
- if (state_ != State::DATA) {
1007
- return APIError::BAD_STATE;
1008
- }
1009
-
1010
1029
  std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
1011
- // Message data starts after padding (frame_header_padding_ = 6)
1012
- size_t payload_len = raw_buffer->size() - frame_header_padding_;
1013
-
1014
- // Calculate varint sizes for header components
1015
- size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
1016
- size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
1017
- size_t total_header_len = 1 + size_varint_len + type_varint_len;
1030
+ uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
1018
1031
 
1019
- if (total_header_len > frame_header_padding_) {
1020
- // Header is too large to fit in the padding
1021
- return APIError::BAD_ARG;
1022
- }
1032
+ // Use write_protobuf_packets with a single packet
1033
+ std::vector<PacketInfo> packets;
1034
+ packets.emplace_back(type, 0, payload_len);
1023
1035
 
1024
- // Calculate where to start writing the header
1025
- // The header starts at the latest possible position to minimize unused padding
1026
- //
1027
- // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
1028
- // [0-2] - Unused padding
1029
- // [3] - 0x00 indicator byte
1030
- // [4] - Payload size varint (1 byte, for sizes 0-127)
1031
- // [5] - Message type varint (1 byte, for types 0-127)
1032
- // [6...] - Actual payload data
1033
- //
1034
- // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
1035
- // [0-1] - Unused padding
1036
- // [2] - 0x00 indicator byte
1037
- // [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
1038
- // [5] - Message type varint (1 byte, for types 0-127)
1039
- // [6...] - Actual payload data
1040
- //
1041
- // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
1042
- // [0] - 0x00 indicator byte
1043
- // [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
1044
- // [4-5] - Message type varint (2 bytes, for types 128-32767)
1045
- // [6...] - Actual payload data
1046
- uint8_t *buf_start = raw_buffer->data();
1047
- size_t header_offset = frame_header_padding_ - total_header_len;
1048
-
1049
- // Write the plaintext header
1050
- buf_start[header_offset] = 0x00; // indicator
1051
-
1052
- // Encode size varint directly into buffer
1053
- ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
1054
-
1055
- // Encode type varint directly into buffer
1056
- ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
1057
-
1058
- struct iovec iov;
1059
- // Point iov_base to the beginning of our header (skip unused padding)
1060
- // This ensures we only send the actual header and payload, not the empty padding bytes
1061
- iov.iov_base = buf_start + header_offset;
1062
- iov.iov_len = total_header_len + payload_len;
1063
-
1064
- return write_raw_(&iov, 1);
1036
+ return write_protobuf_packets(buffer, packets);
1065
1037
  }
1066
- APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
1067
- // try send from tx_buf
1068
- while (state_ != State::CLOSED && !tx_buf_.empty()) {
1069
- ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
1070
- if (is_would_block(sent)) {
1071
- break;
1072
- } else if (sent == -1) {
1073
- state_ = State::FAILED;
1074
- HELPER_LOG("Socket write failed with errno %d", errno);
1075
- return APIError::SOCKET_WRITE_FAILED;
1076
- }
1077
- // TODO: inefficient if multiple packets in txbuf
1078
- // replace with deque of buffers
1079
- tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
1038
+
1039
+ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer,
1040
+ const std::vector<PacketInfo> &packets) {
1041
+ if (state_ != State::DATA) {
1042
+ return APIError::BAD_STATE;
1080
1043
  }
1081
1044
 
1082
- return APIError::OK;
1083
- }
1045
+ if (packets.empty()) {
1046
+ return APIError::OK;
1047
+ }
1084
1048
 
1085
- APIError APIPlaintextFrameHelper::close() {
1086
- state_ = State::CLOSED;
1087
- int err = socket_->close();
1088
- if (err == -1)
1089
- return APIError::CLOSE_FAILED;
1090
- return APIError::OK;
1091
- }
1092
- APIError APIPlaintextFrameHelper::shutdown(int how) {
1093
- int err = socket_->shutdown(how);
1094
- if (err == -1)
1095
- return APIError::SHUTDOWN_FAILED;
1096
- if (how == SHUT_RDWR) {
1097
- state_ = State::CLOSED;
1049
+ std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
1050
+ this->reusable_iovs_.clear();
1051
+ this->reusable_iovs_.reserve(packets.size());
1052
+
1053
+ for (const auto &packet : packets) {
1054
+ uint16_t type = packet.message_type;
1055
+ uint16_t offset = packet.offset;
1056
+ uint16_t payload_len = packet.payload_size;
1057
+
1058
+ // Calculate varint sizes for header layout
1059
+ uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
1060
+ uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
1061
+ uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
1062
+
1063
+ // Calculate where to start writing the header
1064
+ // The header starts at the latest possible position to minimize unused padding
1065
+ //
1066
+ // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
1067
+ // [0-2] - Unused padding
1068
+ // [3] - 0x00 indicator byte
1069
+ // [4] - Payload size varint (1 byte, for sizes 0-127)
1070
+ // [5] - Message type varint (1 byte, for types 0-127)
1071
+ // [6...] - Actual payload data
1072
+ //
1073
+ // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
1074
+ // [0-1] - Unused padding
1075
+ // [2] - 0x00 indicator byte
1076
+ // [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
1077
+ // [5] - Message type varint (1 byte, for types 0-127)
1078
+ // [6...] - Actual payload data
1079
+ //
1080
+ // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
1081
+ // [0] - 0x00 indicator byte
1082
+ // [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
1083
+ // [4-5] - Message type varint (2 bytes, for types 128-32767)
1084
+ // [6...] - Actual payload data
1085
+ //
1086
+ // The message starts at offset + frame_header_padding_
1087
+ // So we write the header starting at offset + frame_header_padding_ - total_header_len
1088
+ uint8_t *buf_start = raw_buffer->data() + offset;
1089
+ uint32_t header_offset = frame_header_padding_ - total_header_len;
1090
+
1091
+ // Write the plaintext header
1092
+ buf_start[header_offset] = 0x00; // indicator
1093
+
1094
+ // Encode size varint directly into buffer
1095
+ ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
1096
+
1097
+ // Encode type varint directly into buffer
1098
+ ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
1099
+
1100
+ // Add iovec for this packet (header + payload)
1101
+ struct iovec iov;
1102
+ iov.iov_base = buf_start + header_offset;
1103
+ iov.iov_len = total_header_len + payload_len;
1104
+ this->reusable_iovs_.push_back(iov);
1098
1105
  }
1099
- return APIError::OK;
1106
+
1107
+ // Send all packets in one writev call
1108
+ return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
1100
1109
  }
1101
1110
 
1102
- // Explicit template instantiation for Plaintext
1103
- template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
1104
- const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
1105
- APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
1106
1111
  #endif // USE_API_PLAINTEXT
1107
1112
 
1108
1113
  } // namespace api