esphome 2025.6.2__py3-none-any.whl → 2025.7.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 (601) 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 +42 -20
  22. esphome/components/api/api_connection.cpp +318 -391
  23. esphome/components/api/api_connection.h +206 -126
  24. esphome/components/api/api_frame_helper.cpp +89 -124
  25. esphome/components/api/api_frame_helper.h +57 -45
  26. esphome/components/api/api_pb2.cpp +414 -4350
  27. esphome/components/api/api_pb2.h +287 -198
  28. esphome/components/api/api_pb2_dump.cpp +4333 -0
  29. esphome/components/api/api_pb2_service.cpp +180 -425
  30. esphome/components/api/api_pb2_service.h +7 -6
  31. esphome/components/api/api_pb2_size.h +2 -4
  32. esphome/components/api/api_server.cpp +138 -167
  33. esphome/components/api/api_server.h +66 -12
  34. esphome/components/api/client.py +10 -4
  35. esphome/components/api/list_entities.cpp +36 -105
  36. esphome/components/api/list_entities.h +31 -23
  37. esphome/components/api/proto.h +26 -3
  38. esphome/components/api/subscribe_state.cpp +23 -29
  39. esphome/components/api/subscribe_state.h +26 -19
  40. esphome/components/as5600/as5600.h +0 -1
  41. esphome/components/async_tcp/__init__.py +14 -5
  42. esphome/components/atc_mithermometer/atc_mithermometer.h +0 -1
  43. esphome/components/atm90e32/atm90e32.cpp +2 -1
  44. esphome/components/audio/audio_decoder.cpp +1 -1
  45. esphome/components/audio/audio_transfer_buffer.cpp +2 -2
  46. esphome/components/b_parasite/b_parasite.h +0 -1
  47. esphome/components/bedjet/bedjet_hub.cpp +5 -1
  48. esphome/components/bedjet/climate/bedjet_climate.cpp +5 -1
  49. esphome/components/beken_spi_led_strip/led_strip.cpp +4 -2
  50. esphome/components/bh1750/bh1750.cpp +5 -5
  51. esphome/components/binary_sensor/__init__.py +82 -5
  52. esphome/components/binary_sensor/automation.h +19 -1
  53. esphome/components/binary_sensor/binary_sensor.cpp +12 -30
  54. esphome/components/binary_sensor/binary_sensor.h +11 -25
  55. esphome/components/binary_sensor/filter.cpp +29 -24
  56. esphome/components/binary_sensor/filter.h +20 -10
  57. esphome/components/ble_client/output/ble_binary_output.h +0 -1
  58. esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +5 -1
  59. esphome/components/ble_client/sensor/ble_rssi_sensor.h +0 -1
  60. esphome/components/ble_client/sensor/ble_sensor.cpp +5 -1
  61. esphome/components/ble_client/sensor/ble_sensor.h +0 -1
  62. esphome/components/ble_client/switch/ble_switch.h +0 -1
  63. esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +5 -1
  64. esphome/components/ble_client/text_sensor/ble_text_sensor.h +0 -1
  65. esphome/components/ble_presence/ble_presence_device.h +0 -1
  66. esphome/components/ble_rssi/ble_rssi_sensor.h +0 -1
  67. esphome/components/ble_scanner/ble_scanner.h +0 -1
  68. esphome/components/bluetooth_proxy/bluetooth_connection.h +9 -2
  69. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +16 -6
  70. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -2
  71. esphome/components/bme680/sensor.py +1 -1
  72. esphome/components/bmp581/bmp581.h +0 -2
  73. esphome/components/button/__init__.py +5 -2
  74. esphome/components/camera/__init__.py +1 -0
  75. esphome/components/camera/camera.cpp +22 -0
  76. esphome/components/camera/camera.h +80 -0
  77. esphome/components/canbus/__init__.py +1 -0
  78. esphome/components/cap1188/cap1188.h +0 -1
  79. esphome/components/captive_portal/__init__.py +12 -2
  80. esphome/components/captive_portal/captive_portal.cpp +12 -2
  81. esphome/components/captive_portal/captive_portal.h +5 -2
  82. esphome/components/ccs811/ccs811.h +0 -2
  83. esphome/components/climate/__init__.py +5 -2
  84. esphome/components/cm1106/sensor.py +2 -2
  85. esphome/components/const/__init__.py +2 -0
  86. esphome/components/copy/binary_sensor/copy_binary_sensor.h +0 -1
  87. esphome/components/copy/button/copy_button.h +0 -1
  88. esphome/components/copy/cover/copy_cover.h +0 -1
  89. esphome/components/copy/fan/copy_fan.h +0 -1
  90. esphome/components/copy/lock/copy_lock.h +0 -1
  91. esphome/components/copy/number/copy_number.h +0 -1
  92. esphome/components/copy/select/copy_select.h +0 -1
  93. esphome/components/copy/sensor/copy_sensor.h +0 -1
  94. esphome/components/copy/switch/copy_switch.h +0 -1
  95. esphome/components/copy/text/copy_text.h +0 -1
  96. esphome/components/copy/text_sensor/copy_text_sensor.h +0 -1
  97. esphome/components/cover/__init__.py +5 -2
  98. esphome/components/cs5460a/cs5460a.h +0 -1
  99. esphome/components/datetime/__init__.py +4 -2
  100. esphome/components/debug/__init__.py +20 -0
  101. esphome/components/deep_sleep/__init__.py +43 -9
  102. esphome/components/demo/__init__.py +2 -2
  103. esphome/components/display/display.cpp +4 -3
  104. esphome/components/display/display.h +0 -2
  105. esphome/components/display/display_buffer.cpp +1 -1
  106. esphome/components/ds2484/__init__.py +1 -0
  107. esphome/components/ds2484/ds2484.cpp +209 -0
  108. esphome/components/ds2484/ds2484.h +43 -0
  109. esphome/components/ds2484/one_wire.py +37 -0
  110. esphome/components/duty_time/duty_time_sensor.h +0 -1
  111. esphome/components/ens160_base/ens160_base.h +0 -1
  112. esphome/components/es7210/es7210.h +0 -1
  113. esphome/components/es7243e/es7243e.h +0 -1
  114. esphome/components/es8156/es8156.h +0 -1
  115. esphome/components/es8311/es8311.h +0 -1
  116. esphome/components/es8388/es8388.h +0 -1
  117. esphome/components/esp32/__init__.py +102 -135
  118. esphome/components/esp32/core.cpp +0 -4
  119. esphome/components/esp32/gpio.h +1 -1
  120. esphome/components/esp32/helpers.cpp +69 -0
  121. esphome/components/esp32_ble/ble.cpp +5 -6
  122. esphome/components/esp32_ble/ble.h +29 -14
  123. esphome/components/esp32_ble/ble_event.h +6 -6
  124. esphome/components/esp32_ble_client/ble_client_base.cpp +21 -6
  125. esphome/components/esp32_ble_client/ble_client_base.h +24 -9
  126. esphome/components/esp32_ble_tracker/__init__.py +2 -8
  127. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +5 -5
  128. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +11 -7
  129. esphome/components/esp32_camera/__init__.py +111 -97
  130. esphome/components/esp32_camera/esp32_camera.cpp +41 -31
  131. esphome/components/esp32_camera/esp32_camera.h +35 -30
  132. esphome/components/esp32_camera_web_server/__init__.py +2 -1
  133. esphome/components/esp32_camera_web_server/camera_web_server.cpp +8 -8
  134. esphome/components/esp32_camera_web_server/camera_web_server.h +3 -3
  135. esphome/components/esp32_hall/sensor.py +2 -21
  136. esphome/components/esp32_hosted/__init__.py +101 -0
  137. esphome/components/esp32_hosted/esp32_hosted.py.script +12 -0
  138. esphome/components/esp32_improv/esp32_improv_component.cpp +3 -0
  139. esphome/components/esp32_rmt/__init__.py +0 -58
  140. esphome/components/esp32_rmt_led_strip/led_strip.cpp +77 -63
  141. esphome/components/esp32_rmt_led_strip/led_strip.h +11 -17
  142. esphome/components/esp32_rmt_led_strip/light.py +14 -76
  143. esphome/components/esp32_touch/esp32_touch.h +174 -28
  144. esphome/components/esp32_touch/esp32_touch_common.cpp +162 -0
  145. esphome/components/esp32_touch/esp32_touch_v1.cpp +238 -0
  146. esphome/components/esp32_touch/esp32_touch_v2.cpp +397 -0
  147. esphome/components/esp8266/__init__.py +1 -0
  148. esphome/components/esp8266/gpio.cpp +10 -10
  149. esphome/components/esp8266/helpers.cpp +31 -0
  150. esphome/components/esphome/ota/__init__.py +1 -0
  151. esphome/components/esphome/ota/ota_esphome.cpp +24 -19
  152. esphome/components/ethernet/__init__.py +42 -23
  153. esphome/components/ethernet/esp_eth_phy_jl1101.c +0 -16
  154. esphome/components/ethernet/ethernet_component.cpp +69 -29
  155. esphome/components/ethernet/ethernet_component.h +18 -10
  156. esphome/components/event/__init__.py +5 -2
  157. esphome/components/ezo/ezo.h +0 -1
  158. esphome/components/ezo_pmp/ezo_pmp.h +0 -1
  159. esphome/components/fan/__init__.py +5 -2
  160. esphome/components/feedback/feedback_cover.h +0 -1
  161. esphome/components/font/__init__.py +92 -82
  162. esphome/components/font/font.cpp +9 -2
  163. esphome/components/font/font.h +20 -5
  164. esphome/components/fs3000/fs3000.h +0 -1
  165. esphome/components/gcja5/gcja5.h +0 -1
  166. esphome/components/gl_r01_i2c/__init__.py +0 -0
  167. esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +68 -0
  168. esphome/components/gl_r01_i2c/gl_r01_i2c.h +22 -0
  169. esphome/components/gl_r01_i2c/sensor.py +36 -0
  170. esphome/components/gp8403/gp8403.h +0 -1
  171. esphome/components/gpio/binary_sensor/__init__.py +17 -0
  172. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +77 -3
  173. esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +40 -0
  174. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h +0 -2
  175. esphome/components/he60r/he60r.h +0 -1
  176. esphome/components/heatpumpir/climate.py +2 -1
  177. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  178. esphome/components/heatpumpir/heatpumpir.h +1 -0
  179. esphome/components/honeywellabp2_i2c/honeywellabp2.h +0 -1
  180. esphome/components/host/__init__.py +2 -1
  181. esphome/components/host/helpers.cpp +57 -0
  182. esphome/components/http_request/__init__.py +19 -1
  183. esphome/components/http_request/http_request.h +1 -1
  184. esphome/components/http_request/http_request_arduino.cpp +0 -1
  185. esphome/components/http_request/http_request_arduino.h +1 -0
  186. esphome/components/http_request/http_request_idf.cpp +0 -1
  187. esphome/components/http_request/ota/ota_http_request.cpp +1 -1
  188. esphome/components/http_request/update/http_request_update.cpp +28 -9
  189. esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +3 -9
  190. esphome/components/hydreon_rgxx/sensor.py +1 -1
  191. esphome/components/i2c/__init__.py +23 -11
  192. esphome/components/i2c/i2c_bus.h +8 -1
  193. esphome/components/i2c/i2c_bus_arduino.cpp +4 -3
  194. esphome/components/i2c/i2c_bus_arduino.h +6 -3
  195. esphome/components/i2c/i2c_bus_esp_idf.h +5 -3
  196. esphome/components/i2c_device/i2c_device.h +0 -1
  197. esphome/components/i2s_audio/__init__.py +2 -10
  198. esphome/components/i2s_audio/i2s_audio.cpp +1 -5
  199. esphome/components/i2s_audio/media_player/__init__.py +2 -2
  200. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +2 -2
  201. esphome/components/iaqcore/iaqcore.h +0 -2
  202. esphome/components/image/__init__.py +123 -24
  203. esphome/components/improv_serial/improv_serial_component.cpp +0 -4
  204. esphome/components/ina219/ina219.cpp +7 -0
  205. esphome/components/ina219/ina219.h +1 -0
  206. esphome/components/ina260/ina260.h +0 -2
  207. esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +0 -1
  208. esphome/components/inkplate6/display.py +15 -0
  209. esphome/components/inkplate6/inkplate.cpp +2 -2
  210. esphome/components/integration/integration_sensor.h +0 -1
  211. esphome/components/internal_temperature/internal_temperature.cpp +8 -27
  212. esphome/components/internal_temperature/sensor.py +0 -26
  213. esphome/components/interval/interval.h +0 -2
  214. esphome/components/ld2410/button/__init__.py +3 -3
  215. esphome/components/ld2410/button/factory_reset_button.cpp +9 -0
  216. esphome/components/ld2410/button/{reset_button.h → factory_reset_button.h} +2 -2
  217. esphome/components/ld2410/ld2410.cpp +430 -261
  218. esphome/components/ld2410/ld2410.h +44 -146
  219. esphome/components/ld2410/number/__init__.py +2 -2
  220. esphome/components/ld2410/sensor.py +1 -1
  221. esphome/components/ld2410/switch/__init__.py +1 -1
  222. esphome/components/ld2420/ld2420.cpp +196 -100
  223. esphome/components/ld2420/ld2420.h +46 -118
  224. esphome/components/ld2420/number/__init__.py +2 -2
  225. esphome/components/ld2420/sensor/__init__.py +6 -2
  226. esphome/components/ld2420/sensor/ld2420_sensor.h +1 -1
  227. esphome/components/ld2450/button/__init__.py +3 -3
  228. esphome/components/ld2450/button/factory_reset_button.cpp +9 -0
  229. esphome/components/ld2450/button/{reset_button.h → factory_reset_button.h} +2 -2
  230. esphome/components/ld2450/ld2450.cpp +384 -232
  231. esphome/components/ld2450/ld2450.h +60 -69
  232. esphome/components/ld2450/switch/__init__.py +1 -1
  233. esphome/components/ledc/ledc_output.cpp +1 -63
  234. esphome/components/libretiny/__init__.py +4 -3
  235. esphome/components/libretiny/const.py +5 -0
  236. esphome/components/libretiny/generate_components.py +1 -0
  237. esphome/components/libretiny/helpers.cpp +35 -0
  238. esphome/components/libretiny/lt_component.cpp +5 -3
  239. esphome/components/light/__init__.py +4 -2
  240. esphome/components/light/addressable_light.h +3 -3
  241. esphome/components/light/light_call.cpp +180 -243
  242. esphome/components/light/light_call.h +72 -20
  243. esphome/components/light/light_color_values.h +14 -14
  244. esphome/components/light/light_state.h +15 -13
  245. esphome/components/light/transformers.h +2 -2
  246. esphome/components/ln882x/__init__.py +52 -0
  247. esphome/components/ln882x/boards.py +285 -0
  248. esphome/components/lock/__init__.py +5 -2
  249. esphome/components/logger/__init__.py +40 -3
  250. esphome/components/logger/logger.cpp +47 -12
  251. esphome/components/logger/logger.h +80 -49
  252. esphome/components/logger/logger_esp32.cpp +3 -3
  253. esphome/components/lps22/__init__.py +0 -0
  254. esphome/components/lps22/lps22.cpp +75 -0
  255. esphome/components/lps22/lps22.h +27 -0
  256. esphome/components/lps22/sensor.py +58 -0
  257. esphome/components/ltr390/ltr390.h +0 -1
  258. esphome/components/ltr501/ltr501.h +0 -1
  259. esphome/components/ltr_als_ps/ltr_als_ps.h +0 -1
  260. esphome/components/lvgl/__init__.py +1 -1
  261. esphome/components/lvgl/schemas.py +66 -6
  262. esphome/components/lvgl/styles.py +24 -16
  263. esphome/components/lvgl/widgets/__init__.py +12 -2
  264. esphome/components/lvgl/widgets/lv_bar.py +40 -19
  265. esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp +1 -1
  266. esphome/components/max9611/max9611.h +0 -1
  267. esphome/components/mcp23016/__init__.py +1 -1
  268. esphome/components/mcp23xxx_base/__init__.py +1 -1
  269. esphome/components/mcp4461/__init__.py +1 -1
  270. esphome/components/mcp4461/output/__init__.py +3 -2
  271. esphome/components/mcp9600/mcp9600.h +0 -2
  272. esphome/components/md5/md5.cpp +3 -3
  273. esphome/components/md5/md5.h +1 -6
  274. esphome/components/mdns/__init__.py +22 -11
  275. esphome/components/media_player/__init__.py +4 -3
  276. esphome/components/micro_wake_word/__init__.py +1 -5
  277. esphome/components/micro_wake_word/streaming_model.cpp +2 -2
  278. esphome/components/microphone/microphone.cpp +7 -9
  279. esphome/components/microphone/microphone.h +0 -2
  280. esphome/components/mipi_spi/display.py +1 -0
  281. esphome/components/mmc5603/mmc5603.cpp +1 -1
  282. esphome/components/modbus/modbus.cpp +33 -15
  283. esphome/components/modbus/modbus.h +9 -0
  284. esphome/components/modbus_controller/__init__.py +42 -10
  285. esphome/components/modbus_controller/modbus_controller.cpp +92 -11
  286. esphome/components/modbus_controller/modbus_controller.h +61 -7
  287. esphome/components/mopeka_pro_check/mopeka_pro_check.h +0 -1
  288. esphome/components/mopeka_std_check/mopeka_std_check.h +0 -1
  289. esphome/components/mpl3115a2/mpl3115a2.h +0 -2
  290. esphome/components/mqtt/__init__.py +16 -0
  291. esphome/components/mqtt/mqtt_backend.h +2 -1
  292. esphome/components/mqtt/mqtt_backend_esp32.cpp +126 -45
  293. esphome/components/mqtt/mqtt_backend_esp32.h +106 -4
  294. esphome/components/mqtt/mqtt_client.cpp +15 -9
  295. esphome/components/mqtt/mqtt_client.h +8 -3
  296. esphome/components/ms8607/ms8607.h +0 -1
  297. esphome/components/neopixelbus/light.py +4 -1
  298. esphome/components/neopixelbus/neopixelbus_light.h +1 -1
  299. esphome/components/network/__init__.py +4 -1
  300. esphome/components/network/ip_address.h +1 -0
  301. esphome/components/nextion/__init__.py +16 -0
  302. esphome/components/nextion/base_component.py +1 -0
  303. esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +1 -1
  304. esphome/components/nextion/display.py +14 -4
  305. esphome/components/nextion/nextion.cpp +166 -101
  306. esphome/components/nextion/nextion.h +84 -53
  307. esphome/components/nextion/nextion_commands.cpp +11 -10
  308. esphome/components/nextion/nextion_component.cpp +28 -28
  309. esphome/components/nextion/nextion_component.h +53 -18
  310. esphome/components/nextion/nextion_component_base.h +3 -0
  311. esphome/components/nextion/nextion_upload.cpp +36 -0
  312. esphome/components/nextion/nextion_upload_arduino.cpp +10 -35
  313. esphome/components/nextion/nextion_upload_idf.cpp +9 -33
  314. esphome/components/nextion/sensor/nextion_sensor.cpp +1 -1
  315. esphome/components/nextion/switch/nextion_switch.cpp +1 -1
  316. esphome/components/nextion/text_sensor/nextion_textsensor.cpp +1 -1
  317. esphome/components/nfc/nfc.cpp +3 -22
  318. esphome/components/nfc/nfc.h +3 -3
  319. esphome/components/number/__init__.py +5 -2
  320. esphome/components/online_image/__init__.py +5 -0
  321. esphome/components/online_image/online_image.cpp +6 -2
  322. esphome/components/online_image/online_image.h +4 -1
  323. esphome/components/opentherm/opentherm.cpp +7 -12
  324. esphome/components/openthread/__init__.py +47 -40
  325. esphome/components/openthread/const.py +1 -0
  326. esphome/components/openthread/openthread_esp.cpp +27 -5
  327. esphome/components/opt3001/__init__.py +0 -0
  328. esphome/components/opt3001/opt3001.cpp +122 -0
  329. esphome/components/opt3001/opt3001.h +27 -0
  330. esphome/components/opt3001/sensor.py +35 -0
  331. esphome/components/ota/__init__.py +17 -0
  332. esphome/components/ota/ota_backend.h +27 -1
  333. esphome/components/ota/ota_backend_arduino_esp32.cpp +12 -2
  334. esphome/components/ota/ota_backend_arduino_esp32.h +3 -0
  335. esphome/components/ota/ota_backend_arduino_esp8266.cpp +18 -4
  336. esphome/components/ota/ota_backend_arduino_esp8266.h +3 -0
  337. esphome/components/ota/ota_backend_arduino_libretiny.cpp +12 -2
  338. esphome/components/ota/ota_backend_arduino_libretiny.h +3 -0
  339. esphome/components/ota/ota_backend_arduino_rp2040.cpp +9 -2
  340. esphome/components/ota/ota_backend_arduino_rp2040.h +3 -0
  341. esphome/components/ota/ota_backend_esp_idf.cpp +10 -16
  342. esphome/components/ota/ota_backend_esp_idf.h +1 -0
  343. esphome/components/packages/__init__.py +5 -2
  344. esphome/components/packet_transport/binary_sensor.py +61 -4
  345. esphome/components/packet_transport/packet_transport.cpp +31 -1
  346. esphome/components/packet_transport/packet_transport.h +11 -5
  347. esphome/components/pcf8574/__init__.py +1 -1
  348. esphome/components/pi4ioe5v6408/__init__.py +84 -0
  349. esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +171 -0
  350. esphome/components/pi4ioe5v6408/pi4ioe5v6408.h +70 -0
  351. esphome/components/pmsa003i/pmsa003i.h +0 -1
  352. esphome/components/pmsx003/pmsx003.h +0 -1
  353. esphome/components/pn7150/pn7150.cpp +7 -7
  354. esphome/components/pn7150/pn7150.h +0 -1
  355. esphome/components/pn7160/pn7160.cpp +7 -7
  356. esphome/components/pn7160/pn7160.h +0 -1
  357. esphome/components/preferences/syncer.h +2 -0
  358. esphome/components/prometheus/prometheus_handler.h +1 -1
  359. esphome/components/psram/psram.cpp +0 -20
  360. esphome/components/pulse_counter/pulse_counter_sensor.h +0 -1
  361. esphome/components/pulse_meter/pulse_meter_sensor.cpp +8 -4
  362. esphome/components/pulse_width/pulse_width.h +0 -1
  363. esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +0 -4
  364. esphome/components/pvvx_mithermometer/display/pvvx_display.h +0 -2
  365. esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +0 -1
  366. esphome/components/qr_code/__init__.py +13 -10
  367. esphome/components/qwiic_pir/qwiic_pir.h +0 -1
  368. esphome/components/radon_eye_ble/radon_eye_listener.cpp +1 -1
  369. esphome/components/rc522/rc522.h +0 -1
  370. esphome/components/rdm6300/rdm6300.h +0 -2
  371. esphome/components/remote_base/__init__.py +7 -5
  372. esphome/components/remote_base/remote_base.cpp +24 -21
  373. esphome/components/remote_base/remote_base.h +3 -26
  374. esphome/components/remote_receiver/__init__.py +40 -46
  375. esphome/components/remote_receiver/remote_receiver.h +4 -18
  376. esphome/components/remote_receiver/remote_receiver_esp32.cpp +0 -87
  377. esphome/components/remote_receiver/remote_receiver_esp8266.cpp +1 -1
  378. esphome/components/remote_transmitter/__init__.py +42 -43
  379. esphome/components/remote_transmitter/remote_transmitter.h +2 -14
  380. esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +0 -77
  381. esphome/components/resistance/resistance_sensor.h +0 -1
  382. esphome/components/rp2040/__init__.py +1 -0
  383. esphome/components/rp2040/helpers.cpp +55 -0
  384. esphome/components/rp2040_pio_led_strip/led_strip.cpp +2 -2
  385. esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +0 -4
  386. esphome/components/rtttl/__init__.py +4 -4
  387. esphome/components/rtttl/rtttl.cpp +10 -1
  388. esphome/components/ruuvitag/ruuvitag.h +0 -1
  389. esphome/components/safe_mode/safe_mode.cpp +2 -0
  390. esphome/components/safe_mode/safe_mode.h +4 -1
  391. esphome/components/scd30/scd30.h +0 -1
  392. esphome/components/scd30/sensor.py +2 -2
  393. esphome/components/scd4x/scd4x.cpp +61 -54
  394. esphome/components/scd4x/scd4x.h +17 -15
  395. esphome/components/scd4x/sensor.py +4 -4
  396. esphome/components/script/script.h +0 -2
  397. esphome/components/sdp3x/sensor.py +1 -1
  398. esphome/components/select/__init__.py +5 -2
  399. esphome/components/sen5x/sen5x.h +0 -1
  400. esphome/components/senseair/senseair.h +0 -1
  401. esphome/components/sensor/__init__.py +4 -2
  402. esphome/components/sensor/filter.cpp +1 -1
  403. esphome/components/sensor/sensor.cpp +12 -6
  404. esphome/components/sensor/sensor.h +13 -5
  405. esphome/components/servo/servo.h +0 -1
  406. esphome/components/sfa30/sfa30.h +0 -1
  407. esphome/components/sgp30/sgp30.h +0 -1
  408. esphome/components/sgp4x/sgp4x.h +0 -1
  409. esphome/components/shelly_dimmer/stm32flash.cpp +1 -2
  410. esphome/components/sht4x/sht4x.h +0 -1
  411. esphome/components/sm300d2/sm300d2.h +0 -2
  412. esphome/components/smt100/sensor.py +8 -4
  413. esphome/components/smt100/smt100.cpp +5 -5
  414. esphome/components/smt100/smt100.h +3 -3
  415. esphome/components/sn74hc595/__init__.py +1 -1
  416. esphome/components/sn74hc595/sn74hc595.cpp +5 -4
  417. esphome/components/sntp/sntp_component.cpp +9 -3
  418. esphome/components/sntp/time.py +2 -0
  419. esphome/components/socket/__init__.py +17 -0
  420. esphome/components/spi/__init__.py +27 -6
  421. esphome/components/spi/spi.cpp +3 -2
  422. esphome/components/spi/spi.h +9 -3
  423. esphome/components/spi/spi_arduino.cpp +3 -5
  424. esphome/components/spi/spi_esp_idf.cpp +40 -21
  425. esphome/components/spi_led_strip/spi_led_strip.cpp +1 -1
  426. esphome/components/sps30/sps30.h +0 -1
  427. esphome/components/ssd1306_base/ssd1306_base.cpp +1 -1
  428. esphome/components/st7701s/st7701s.cpp +0 -4
  429. esphome/components/status/status_binary_sensor.h +0 -2
  430. esphome/components/substitutions/__init__.py +76 -19
  431. esphome/components/substitutions/jinja.py +99 -0
  432. esphome/components/sun/sun.cpp +3 -4
  433. esphome/components/switch/__init__.py +5 -2
  434. esphome/components/switch/binary_sensor/switch_binary_sensor.h +0 -1
  435. esphome/components/sx126x/__init__.py +317 -0
  436. esphome/components/sx126x/automation.h +62 -0
  437. esphome/components/sx126x/packet_transport/__init__.py +26 -0
  438. esphome/components/sx126x/packet_transport/sx126x_transport.cpp +26 -0
  439. esphome/components/sx126x/packet_transport/sx126x_transport.h +25 -0
  440. esphome/components/sx126x/sx126x.cpp +523 -0
  441. esphome/components/sx126x/sx126x.h +140 -0
  442. esphome/components/sx126x/sx126x_reg.h +163 -0
  443. esphome/components/sx127x/__init__.py +325 -0
  444. esphome/components/sx127x/automation.h +62 -0
  445. esphome/components/sx127x/packet_transport/__init__.py +26 -0
  446. esphome/components/sx127x/packet_transport/sx127x_transport.cpp +26 -0
  447. esphome/components/sx127x/packet_transport/sx127x_transport.h +25 -0
  448. esphome/components/sx127x/sx127x.cpp +498 -0
  449. esphome/components/sx127x/sx127x.h +128 -0
  450. esphome/components/sx127x/sx127x_reg.h +295 -0
  451. esphome/components/syslog/esphome_syslog.cpp +5 -3
  452. esphome/components/syslog/esphome_syslog.h +1 -1
  453. esphome/components/tca9555/__init__.py +1 -1
  454. esphome/components/template/binary_sensor/template_binary_sensor.cpp +1 -9
  455. esphome/components/text/__init__.py +5 -2
  456. esphome/components/text_sensor/__init__.py +5 -2
  457. esphome/components/thermostat/thermostat_climate.cpp +34 -31
  458. esphome/components/thermostat/thermostat_climate.h +43 -39
  459. esphome/components/time/__init__.py +16 -2
  460. esphome/components/time/real_time_clock.cpp +4 -0
  461. esphome/components/time/real_time_clock.h +5 -1
  462. esphome/components/tlc5971/tlc5971.cpp +4 -1
  463. esphome/components/tmp1075/tmp1075.h +0 -2
  464. esphome/components/tof10120/tof10120_sensor.h +0 -1
  465. esphome/components/tormatic/tormatic_cover.h +0 -1
  466. esphome/components/total_daily_energy/total_daily_energy.h +0 -1
  467. esphome/components/tsl2591/tsl2591.cpp +1 -1
  468. esphome/components/ttp229_bsf/ttp229_bsf.h +0 -1
  469. esphome/components/ttp229_lsf/ttp229_lsf.h +0 -1
  470. esphome/components/tx20/tx20.cpp +2 -2
  471. esphome/components/uart/__init__.py +18 -0
  472. esphome/components/uart/uart_component_esp_idf.cpp +1 -5
  473. esphome/components/update/__init__.py +5 -2
  474. esphome/components/update/update_entity.h +8 -0
  475. esphome/components/usb_host/__init__.py +5 -2
  476. esphome/components/valve/__init__.py +5 -2
  477. esphome/components/vbus/vbus.h +0 -1
  478. esphome/components/veml3235/veml3235.h +0 -1
  479. esphome/components/veml7700/veml7700.h +0 -1
  480. esphome/components/vl53l0x/vl53l0x_sensor.h +0 -1
  481. esphome/components/voice_assistant/voice_assistant.cpp +4 -4
  482. esphome/components/watchdog/watchdog.cpp +0 -4
  483. esphome/components/waveshare_epaper/waveshare_epaper.cpp +6 -6
  484. esphome/components/web_server/__init__.py +34 -19
  485. esphome/components/web_server/ota/__init__.py +32 -0
  486. esphome/components/web_server/ota/ota_web_server.cpp +210 -0
  487. esphome/components/web_server/ota/ota_web_server.h +26 -0
  488. esphome/components/web_server/web_server.cpp +311 -430
  489. esphome/components/web_server/web_server.h +33 -23
  490. esphome/components/web_server/web_server_v1.cpp +4 -5
  491. esphome/components/web_server_base/__init__.py +5 -2
  492. esphome/components/web_server_base/web_server_base.cpp +2 -94
  493. esphome/components/web_server_base/web_server_base.h +5 -25
  494. esphome/components/web_server_idf/multipart.cpp +254 -0
  495. esphome/components/web_server_idf/multipart.h +86 -0
  496. esphome/components/web_server_idf/utils.cpp +32 -0
  497. esphome/components/web_server_idf/utils.h +10 -0
  498. esphome/components/web_server_idf/web_server_idf.cpp +162 -16
  499. esphome/components/web_server_idf/web_server_idf.h +11 -10
  500. esphome/components/wiegand/wiegand.cpp +2 -2
  501. esphome/components/wifi/__init__.py +18 -0
  502. esphome/components/wifi/wifi_component.cpp +17 -22
  503. esphome/components/wifi/wifi_component.h +27 -23
  504. esphome/components/wifi/wifi_component_esp32_arduino.cpp +52 -59
  505. esphome/components/wifi/wifi_component_esp8266.cpp +46 -46
  506. esphome/components/wifi/wifi_component_esp_idf.cpp +35 -36
  507. esphome/components/wifi/wifi_component_libretiny.cpp +26 -27
  508. esphome/components/wifi/wifi_component_pico_w.cpp +3 -3
  509. esphome/components/wifi_info/wifi_info_text_sensor.cpp +6 -6
  510. esphome/components/wireguard/__init__.py +2 -11
  511. esphome/components/xiaomi_ble/xiaomi_ble.cpp +13 -1
  512. esphome/components/xiaomi_ble/xiaomi_ble.h +1 -0
  513. esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +0 -1
  514. esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +0 -1
  515. esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +0 -1
  516. esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +0 -1
  517. esphome/components/xiaomi_gcls002/xiaomi_gcls002.h +0 -1
  518. esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +0 -1
  519. esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h +0 -1
  520. esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h +0 -1
  521. esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h +0 -1
  522. esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +0 -1
  523. esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +0 -1
  524. esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +0 -1
  525. esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +0 -1
  526. esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h +0 -1
  527. esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +0 -1
  528. esphome/components/xiaomi_miscale/xiaomi_miscale.h +0 -1
  529. esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +0 -1
  530. esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h +0 -1
  531. esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +0 -1
  532. esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h +0 -1
  533. esphome/components/xiaomi_xmwsdj04mmc/__init__.py +0 -0
  534. esphome/components/xiaomi_xmwsdj04mmc/sensor.py +77 -0
  535. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +77 -0
  536. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +36 -0
  537. esphome/components/zio_ultrasonic/zio_ultrasonic.h +0 -2
  538. esphome/components/zyaura/zyaura.h +0 -1
  539. esphome/config.py +88 -22
  540. esphome/config_helpers.py +74 -1
  541. esphome/config_validation.py +12 -1
  542. esphome/const.py +65 -10
  543. esphome/core/__init__.py +18 -2
  544. esphome/core/application.cpp +163 -10
  545. esphome/core/application.h +145 -165
  546. esphome/core/area.h +19 -0
  547. esphome/core/automation.h +58 -9
  548. esphome/core/color.cpp +3 -5
  549. esphome/core/color.h +16 -16
  550. esphome/core/component.cpp +151 -18
  551. esphome/core/component.h +98 -4
  552. esphome/core/component_iterator.cpp +7 -7
  553. esphome/core/component_iterator.h +9 -7
  554. esphome/core/config.py +155 -6
  555. esphome/core/controller.cpp +4 -2
  556. esphome/core/controller.h +1 -1
  557. esphome/core/datatypes.h +2 -2
  558. esphome/core/defines.h +17 -2
  559. esphome/core/device.h +20 -0
  560. esphome/core/entity_base.cpp +20 -15
  561. esphome/core/entity_base.h +76 -0
  562. esphome/core/entity_helpers.py +162 -1
  563. esphome/core/event_pool.h +81 -0
  564. esphome/core/helpers.cpp +75 -230
  565. esphome/core/helpers.h +164 -104
  566. esphome/core/lock_free_queue.h +151 -0
  567. esphome/core/log.cpp +2 -2
  568. esphome/core/log.h +2 -0
  569. esphome/core/optional.h +5 -0
  570. esphome/core/ring_buffer.cpp +2 -2
  571. esphome/core/scheduler.cpp +278 -103
  572. esphome/core/scheduler.h +157 -17
  573. esphome/core/time.cpp +5 -5
  574. esphome/core/time.h +5 -5
  575. esphome/cpp_generator.py +17 -0
  576. esphome/cpp_helpers.py +0 -22
  577. esphome/cpp_types.py +3 -1
  578. esphome/dashboard/entries.py +1 -1
  579. esphome/dashboard/util/text.py +5 -21
  580. esphome/dashboard/web_server.py +9 -1
  581. esphome/helpers.py +47 -0
  582. esphome/loader.py +15 -1
  583. esphome/pins.py +14 -8
  584. esphome/wizard.py +16 -3
  585. esphome/writer.py +21 -3
  586. esphome/yaml_util.py +0 -2
  587. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/METADATA +10 -9
  588. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/RECORD +593 -533
  589. esphome/components/esp32_ble/ble_event_pool.h +0 -72
  590. esphome/components/esp32_ble/queue.h +0 -85
  591. esphome/components/esp32_hall/esp32_hall.cpp +0 -25
  592. esphome/components/esp32_hall/esp32_hall.h +0 -23
  593. esphome/components/esp32_touch/esp32_touch.cpp +0 -355
  594. esphome/components/ld2410/button/reset_button.cpp +0 -9
  595. esphome/components/ld2450/button/reset_button.cpp +0 -9
  596. esphome/components/openthread/tlv.py +0 -65
  597. /esphome/{dashboard/enum.py → enum.py} +0 -0
  598. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/WHEEL +0 -0
  599. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/entry_points.txt +0 -0
  600. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/licenses/LICENSE +0 -0
  601. {esphome-2025.6.2.dist-info → esphome-2025.7.0b1.dist-info}/top_level.txt +0 -0
@@ -46,29 +46,60 @@ static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-N
46
46
  static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network";
47
47
  #endif
48
48
 
49
- UrlMatch match_url(const std::string &url, bool only_domain = false) {
50
- UrlMatch match;
51
- match.valid = false;
52
- size_t domain_end = url.find('/', 1);
53
- if (domain_end == std::string::npos)
54
- return match;
55
- match.domain = url.substr(1, domain_end - 1);
56
- if (only_domain) {
57
- match.valid = true;
49
+ // Parse URL and return match info
50
+ static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) {
51
+ UrlMatch match{};
52
+
53
+ // URL must start with '/'
54
+ if (url_len < 2 || url_ptr[0] != '/') {
58
55
  return match;
59
56
  }
60
- if (url.length() == domain_end - 1)
57
+
58
+ // Skip leading '/'
59
+ const char *start = url_ptr + 1;
60
+ const char *end = url_ptr + url_len;
61
+
62
+ // Find domain (everything up to next '/' or end)
63
+ const char *domain_end = (const char *) memchr(start, '/', end - start);
64
+ if (!domain_end) {
65
+ // No second slash found - original behavior returns invalid
61
66
  return match;
62
- size_t id_begin = domain_end + 1;
63
- size_t id_end = url.find('/', id_begin);
67
+ }
68
+
69
+ // Set domain
70
+ match.domain = start;
71
+ match.domain_len = domain_end - start;
64
72
  match.valid = true;
65
- if (id_end == std::string::npos) {
66
- match.id = url.substr(id_begin, url.length() - id_begin);
73
+
74
+ if (only_domain) {
75
+ return match;
76
+ }
77
+
78
+ // Parse ID if present
79
+ if (domain_end + 1 >= end) {
80
+ return match; // Nothing after domain slash
81
+ }
82
+
83
+ const char *id_start = domain_end + 1;
84
+ const char *id_end = (const char *) memchr(id_start, '/', end - id_start);
85
+
86
+ if (!id_end) {
87
+ // No more slashes, entire remaining string is ID
88
+ match.id = id_start;
89
+ match.id_len = end - id_start;
67
90
  return match;
68
91
  }
69
- match.id = url.substr(id_begin, id_end - id_begin);
70
- size_t method_begin = id_end + 1;
71
- match.method = url.substr(method_begin, url.length() - method_begin);
92
+
93
+ // Set ID
94
+ match.id = id_start;
95
+ match.id_len = id_end - id_start;
96
+
97
+ // Parse method if present
98
+ if (id_end + 1 < end) {
99
+ match.method = id_end + 1;
100
+ match.method_len = end - (id_end + 1);
101
+ }
102
+
72
103
  return match;
73
104
  }
74
105
 
@@ -91,10 +122,19 @@ void DeferredUpdateEventSource::process_deferred_queue_() {
91
122
  while (!deferred_queue_.empty()) {
92
123
  DeferredEvent &de = deferred_queue_.front();
93
124
  std::string message = de.message_generator_(web_server_, de.source_);
94
- if (this->try_send(message.c_str(), "state")) {
125
+ if (this->send(message.c_str(), "state") != DISCARDED) {
95
126
  // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
96
127
  deferred_queue_.erase(deferred_queue_.begin());
128
+ this->consecutive_send_failures_ = 0; // Reset failure count on successful send
97
129
  } else {
130
+ this->consecutive_send_failures_++;
131
+ if (this->consecutive_send_failures_ >= MAX_CONSECUTIVE_SEND_FAILURES) {
132
+ // Too many failures, connection is likely dead
133
+ ESP_LOGW(TAG, "Closing stuck EventSource connection after %" PRIu16 " failed sends",
134
+ this->consecutive_send_failures_);
135
+ this->close();
136
+ this->deferred_queue_.clear();
137
+ }
98
138
  break;
99
139
  }
100
140
  }
@@ -131,8 +171,10 @@ void DeferredUpdateEventSource::deferrable_send_state(void *source, const char *
131
171
  deq_push_back_with_dedup_(source, message_generator);
132
172
  } else {
133
173
  std::string message = message_generator(web_server_, source);
134
- if (!this->try_send(message.c_str(), "state")) {
174
+ if (this->send(message.c_str(), "state") == DISCARDED) {
135
175
  deq_push_back_with_dedup_(source, message_generator);
176
+ } else {
177
+ this->consecutive_send_failures_ = 0; // Reset failure count on successful send
136
178
  }
137
179
  }
138
180
  }
@@ -171,8 +213,8 @@ void DeferredUpdateEventSourceList::add_new_client(WebServer *ws, AsyncWebServer
171
213
  ws->defer([this, ws, es]() { this->on_client_connect_(ws, es); });
172
214
  });
173
215
 
174
- es->onDisconnect([this, ws](AsyncEventSource *source, AsyncEventSourceClient *client) {
175
- ws->defer([this, source]() { this->on_client_disconnect_((DeferredUpdateEventSource *) source); });
216
+ es->onDisconnect([this, ws, es](AsyncEventSourceClient *client) {
217
+ ws->defer([this, es]() { this->on_client_disconnect_((DeferredUpdateEventSource *) es); });
176
218
  });
177
219
 
178
220
  es->handleRequest(request);
@@ -184,6 +226,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUp
184
226
  std::string message = ws->get_config_json();
185
227
  source->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
186
228
 
229
+ #ifdef USE_WEBSERVER_SORTING
187
230
  for (auto &group : ws->sorting_groups_) {
188
231
  message = json::build_json([group](JsonObject root) {
189
232
  root["name"] = group.second.name;
@@ -193,6 +236,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUp
193
236
  // up to 31 groups should be able to be queued initially without defer
194
237
  source->try_send_nodefer(message.c_str(), "sorting_group");
195
238
  }
239
+ #endif
196
240
 
197
241
  source->entities_iterator_.begin(ws->include_internal_);
198
242
 
@@ -211,11 +255,7 @@ void DeferredUpdateEventSourceList::on_client_disconnect_(DeferredUpdateEventSou
211
255
  }
212
256
  #endif
213
257
 
214
- WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {
215
- #ifdef USE_ESP32
216
- to_schedule_lock_ = xSemaphoreCreateMutex();
217
- #endif
218
- }
258
+ WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {}
219
259
 
220
260
  #ifdef USE_WEBSERVER_CSS_INCLUDE
221
261
  void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; }
@@ -228,7 +268,11 @@ std::string WebServer::get_config_json() {
228
268
  return json::build_json([this](JsonObject root) {
229
269
  root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
230
270
  root["comment"] = App.get_comment();
231
- root["ota"] = this->allow_ota_;
271
+ #ifdef USE_WEBSERVER_OTA
272
+ root["ota"] = true; // web_server OTA platform is configured
273
+ #else
274
+ root["ota"] = false;
275
+ #endif
232
276
  root["log"] = this->expose_log_;
233
277
  root["lang"] = "en";
234
278
  });
@@ -243,7 +287,8 @@ void WebServer::setup() {
243
287
  if (logger::global_logger != nullptr && this->expose_log_) {
244
288
  logger::global_logger->add_on_log_callback(
245
289
  // logs are not deferred, the memory overhead would be too large
246
- [this](int level, const char *tag, const char *message) {
290
+ [this](int level, const char *tag, const char *message, size_t message_len) {
291
+ (void) message_len;
247
292
  this->events_.try_send_nodefer(message, "log", millis());
248
293
  });
249
294
  }
@@ -254,33 +299,13 @@ void WebServer::setup() {
254
299
  #endif
255
300
  this->base_->add_handler(this);
256
301
 
257
- if (this->allow_ota_)
258
- this->base_->add_ota_handler();
302
+ // OTA is now handled by the web_server OTA platform
259
303
 
260
304
  // doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
261
305
  // getting a lot of events
262
306
  this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); });
263
307
  }
264
- void WebServer::loop() {
265
- #ifdef USE_ESP32
266
- if (xSemaphoreTake(this->to_schedule_lock_, 0L)) {
267
- std::function<void()> fn;
268
- if (!to_schedule_.empty()) {
269
- // scheduler execute things out of order which may lead to incorrect state
270
- // this->defer(std::move(to_schedule_.front()));
271
- // let's execute it directly from the loop
272
- fn = std::move(to_schedule_.front());
273
- to_schedule_.pop_front();
274
- }
275
- xSemaphoreGive(this->to_schedule_lock_);
276
- if (fn) {
277
- fn();
278
- }
279
- }
280
- #endif
281
-
282
- this->events_.loop();
283
- }
308
+ void WebServer::loop() { this->events_.loop(); }
284
309
  void WebServer::dump_config() {
285
310
  ESP_LOGCONFIG(TAG,
286
311
  "Web Server:\n"
@@ -291,14 +316,23 @@ float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f
291
316
 
292
317
  #ifdef USE_WEBSERVER_LOCAL
293
318
  void WebServer::handle_index_request(AsyncWebServerRequest *request) {
319
+ #ifndef USE_ESP8266
320
+ AsyncWebServerResponse *response = request->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
321
+ #else
294
322
  AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
323
+ #endif
295
324
  response->addHeader("Content-Encoding", "gzip");
296
325
  request->send(response);
297
326
  }
298
327
  #elif USE_WEBSERVER_VERSION >= 2
299
328
  void WebServer::handle_index_request(AsyncWebServerRequest *request) {
329
+ #ifndef USE_ESP8266
330
+ AsyncWebServerResponse *response =
331
+ request->beginResponse(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
332
+ #else
300
333
  AsyncWebServerResponse *response =
301
334
  request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
335
+ #endif
302
336
  // No gzip header here because the HTML file is so small
303
337
  request->send(response);
304
338
  }
@@ -317,8 +351,13 @@ void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) {
317
351
 
318
352
  #ifdef USE_WEBSERVER_CSS_INCLUDE
319
353
  void WebServer::handle_css_request(AsyncWebServerRequest *request) {
354
+ #ifndef USE_ESP8266
355
+ AsyncWebServerResponse *response =
356
+ request->beginResponse(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
357
+ #else
320
358
  AsyncWebServerResponse *response =
321
359
  request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
360
+ #endif
322
361
  response->addHeader("Content-Encoding", "gzip");
323
362
  request->send(response);
324
363
  }
@@ -326,8 +365,13 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) {
326
365
 
327
366
  #ifdef USE_WEBSERVER_JS_INCLUDE
328
367
  void WebServer::handle_js_request(AsyncWebServerRequest *request) {
368
+ #ifndef USE_ESP8266
369
+ AsyncWebServerResponse *response =
370
+ request->beginResponse(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
371
+ #else
329
372
  AsyncWebServerResponse *response =
330
373
  request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
374
+ #endif
331
375
  response->addHeader("Content-Encoding", "gzip");
332
376
  request->send(response);
333
377
  }
@@ -351,6 +395,12 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
351
395
  set_json_value(root, obj, sensor, value, start_config); \
352
396
  (root)["state"] = state;
353
397
 
398
+ // Helper to get request detail parameter
399
+ static JsonDetail get_request_detail(AsyncWebServerRequest *request) {
400
+ auto *param = request->getParam("detail");
401
+ return (param && param->value() == "all") ? DETAIL_ALL : DETAIL_STATE;
402
+ }
403
+
354
404
  #ifdef USE_SENSOR
355
405
  void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
356
406
  if (this->events_.empty())
@@ -359,14 +409,10 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
359
409
  }
360
410
  void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
361
411
  for (sensor::Sensor *obj : App.get_sensors()) {
362
- if (obj->get_object_id() != match.id)
412
+ if (!match.id_equals(obj->get_object_id()))
363
413
  continue;
364
- if (request->method() == HTTP_GET && match.method.empty()) {
365
- auto detail = DETAIL_STATE;
366
- auto *param = request->getParam("detail");
367
- if (param && param->value() == "all") {
368
- detail = DETAIL_ALL;
369
- }
414
+ if (request->method() == HTTP_GET && match.method_empty()) {
415
+ auto detail = get_request_detail(request);
370
416
  std::string data = this->sensor_json(obj, obj->state, detail);
371
417
  request->send(200, "application/json", data.c_str());
372
418
  return;
@@ -392,12 +438,7 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
392
438
  }
393
439
  set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
394
440
  if (start_config == DETAIL_ALL) {
395
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
396
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
397
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
398
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
399
- }
400
- }
441
+ this->add_sorting_info_(root, obj);
401
442
  if (!obj->get_unit_of_measurement().empty())
402
443
  root["uom"] = obj->get_unit_of_measurement();
403
444
  }
@@ -413,14 +454,10 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
413
454
  }
414
455
  void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
415
456
  for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
416
- if (obj->get_object_id() != match.id)
457
+ if (!match.id_equals(obj->get_object_id()))
417
458
  continue;
418
- if (request->method() == HTTP_GET && match.method.empty()) {
419
- auto detail = DETAIL_STATE;
420
- auto *param = request->getParam("detail");
421
- if (param && param->value() == "all") {
422
- detail = DETAIL_ALL;
423
- }
459
+ if (request->method() == HTTP_GET && match.method_empty()) {
460
+ auto detail = get_request_detail(request);
424
461
  std::string data = this->text_sensor_json(obj, obj->state, detail);
425
462
  request->send(200, "application/json", data.c_str());
426
463
  return;
@@ -441,12 +478,7 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
441
478
  return json::build_json([this, obj, value, start_config](JsonObject root) {
442
479
  set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
443
480
  if (start_config == DETAIL_ALL) {
444
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
445
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
446
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
447
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
448
- }
449
- }
481
+ this->add_sorting_info_(root, obj);
450
482
  }
451
483
  });
452
484
  }
@@ -460,25 +492,21 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
460
492
  }
461
493
  void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
462
494
  for (switch_::Switch *obj : App.get_switches()) {
463
- if (obj->get_object_id() != match.id)
495
+ if (!match.id_equals(obj->get_object_id()))
464
496
  continue;
465
497
 
466
- if (request->method() == HTTP_GET && match.method.empty()) {
467
- auto detail = DETAIL_STATE;
468
- auto *param = request->getParam("detail");
469
- if (param && param->value() == "all") {
470
- detail = DETAIL_ALL;
471
- }
498
+ if (request->method() == HTTP_GET && match.method_empty()) {
499
+ auto detail = get_request_detail(request);
472
500
  std::string data = this->switch_json(obj, obj->state, detail);
473
501
  request->send(200, "application/json", data.c_str());
474
- } else if (match.method == "toggle") {
475
- this->schedule_([obj]() { obj->toggle(); });
502
+ } else if (match.method_equals("toggle")) {
503
+ this->defer([obj]() { obj->toggle(); });
476
504
  request->send(200);
477
- } else if (match.method == "turn_on") {
478
- this->schedule_([obj]() { obj->turn_on(); });
505
+ } else if (match.method_equals("turn_on")) {
506
+ this->defer([obj]() { obj->turn_on(); });
479
507
  request->send(200);
480
- } else if (match.method == "turn_off") {
481
- this->schedule_([obj]() { obj->turn_off(); });
508
+ } else if (match.method_equals("turn_off")) {
509
+ this->defer([obj]() { obj->turn_off(); });
482
510
  request->send(200);
483
511
  } else {
484
512
  request->send(404);
@@ -498,12 +526,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
498
526
  set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
499
527
  if (start_config == DETAIL_ALL) {
500
528
  root["assumed_state"] = obj->assumed_state();
501
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
502
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
503
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
504
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
505
- }
506
- }
529
+ this->add_sorting_info_(root, obj);
507
530
  }
508
531
  });
509
532
  }
@@ -512,18 +535,14 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
512
535
  #ifdef USE_BUTTON
513
536
  void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
514
537
  for (button::Button *obj : App.get_buttons()) {
515
- if (obj->get_object_id() != match.id)
538
+ if (!match.id_equals(obj->get_object_id()))
516
539
  continue;
517
- if (request->method() == HTTP_GET && match.method.empty()) {
518
- auto detail = DETAIL_STATE;
519
- auto *param = request->getParam("detail");
520
- if (param && param->value() == "all") {
521
- detail = DETAIL_ALL;
522
- }
540
+ if (request->method() == HTTP_GET && match.method_empty()) {
541
+ auto detail = get_request_detail(request);
523
542
  std::string data = this->button_json(obj, detail);
524
543
  request->send(200, "application/json", data.c_str());
525
- } else if (match.method == "press") {
526
- this->schedule_([obj]() { obj->press(); });
544
+ } else if (match.method_equals("press")) {
545
+ this->defer([obj]() { obj->press(); });
527
546
  request->send(200);
528
547
  return;
529
548
  } else {
@@ -543,33 +562,24 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
543
562
  return json::build_json([this, obj, start_config](JsonObject root) {
544
563
  set_json_id(root, obj, "button-" + obj->get_object_id(), start_config);
545
564
  if (start_config == DETAIL_ALL) {
546
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
547
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
548
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
549
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
550
- }
551
- }
565
+ this->add_sorting_info_(root, obj);
552
566
  }
553
567
  });
554
568
  }
555
569
  #endif
556
570
 
557
571
  #ifdef USE_BINARY_SENSOR
558
- void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
572
+ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
559
573
  if (this->events_.empty())
560
574
  return;
561
575
  this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator);
562
576
  }
563
577
  void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
564
578
  for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
565
- if (obj->get_object_id() != match.id)
579
+ if (!match.id_equals(obj->get_object_id()))
566
580
  continue;
567
- if (request->method() == HTTP_GET && match.method.empty()) {
568
- auto detail = DETAIL_STATE;
569
- auto *param = request->getParam("detail");
570
- if (param && param->value() == "all") {
571
- detail = DETAIL_ALL;
572
- }
581
+ if (request->method() == HTTP_GET && match.method_empty()) {
582
+ auto detail = get_request_detail(request);
573
583
  std::string data = this->binary_sensor_json(obj, obj->state, detail);
574
584
  request->send(200, "application/json", data.c_str());
575
585
  return;
@@ -590,12 +600,7 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
590
600
  set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
591
601
  start_config);
592
602
  if (start_config == DETAIL_ALL) {
593
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
594
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
595
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
596
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
597
- }
598
- }
603
+ this->add_sorting_info_(root, obj);
599
604
  }
600
605
  });
601
606
  }
@@ -609,22 +614,18 @@ void WebServer::on_fan_update(fan::Fan *obj) {
609
614
  }
610
615
  void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
611
616
  for (fan::Fan *obj : App.get_fans()) {
612
- if (obj->get_object_id() != match.id)
617
+ if (!match.id_equals(obj->get_object_id()))
613
618
  continue;
614
619
 
615
- if (request->method() == HTTP_GET && match.method.empty()) {
616
- auto detail = DETAIL_STATE;
617
- auto *param = request->getParam("detail");
618
- if (param && param->value() == "all") {
619
- detail = DETAIL_ALL;
620
- }
620
+ if (request->method() == HTTP_GET && match.method_empty()) {
621
+ auto detail = get_request_detail(request);
621
622
  std::string data = this->fan_json(obj, detail);
622
623
  request->send(200, "application/json", data.c_str());
623
- } else if (match.method == "toggle") {
624
- this->schedule_([obj]() { obj->toggle().perform(); });
624
+ } else if (match.method_equals("toggle")) {
625
+ this->defer([obj]() { obj->toggle().perform(); });
625
626
  request->send(200);
626
- } else if (match.method == "turn_on" || match.method == "turn_off") {
627
- auto call = match.method == "turn_on" ? obj->turn_on() : obj->turn_off();
627
+ } else if (match.method_equals("turn_on") || match.method_equals("turn_off")) {
628
+ auto call = match.method_equals("turn_on") ? obj->turn_on() : obj->turn_off();
628
629
 
629
630
  if (request->hasParam("speed_level")) {
630
631
  auto speed_level = request->getParam("speed_level")->value();
@@ -653,7 +654,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
653
654
  return;
654
655
  }
655
656
  }
656
- this->schedule_([call]() mutable { call.perform(); });
657
+ this->defer([call]() mutable { call.perform(); });
657
658
  request->send(200);
658
659
  } else {
659
660
  request->send(404);
@@ -680,12 +681,7 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
680
681
  if (obj->get_traits().supports_oscillation())
681
682
  root["oscillation"] = obj->oscillating;
682
683
  if (start_config == DETAIL_ALL) {
683
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
684
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
685
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
686
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
687
- }
688
- }
684
+ this->add_sorting_info_(root, obj);
689
685
  }
690
686
  });
691
687
  }
@@ -699,21 +695,17 @@ void WebServer::on_light_update(light::LightState *obj) {
699
695
  }
700
696
  void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
701
697
  for (light::LightState *obj : App.get_lights()) {
702
- if (obj->get_object_id() != match.id)
698
+ if (!match.id_equals(obj->get_object_id()))
703
699
  continue;
704
700
 
705
- if (request->method() == HTTP_GET && match.method.empty()) {
706
- auto detail = DETAIL_STATE;
707
- auto *param = request->getParam("detail");
708
- if (param && param->value() == "all") {
709
- detail = DETAIL_ALL;
710
- }
701
+ if (request->method() == HTTP_GET && match.method_empty()) {
702
+ auto detail = get_request_detail(request);
711
703
  std::string data = this->light_json(obj, detail);
712
704
  request->send(200, "application/json", data.c_str());
713
- } else if (match.method == "toggle") {
714
- this->schedule_([obj]() { obj->toggle().perform(); });
705
+ } else if (match.method_equals("toggle")) {
706
+ this->defer([obj]() { obj->toggle().perform(); });
715
707
  request->send(200);
716
- } else if (match.method == "turn_on") {
708
+ } else if (match.method_equals("turn_on")) {
717
709
  auto call = obj->turn_on();
718
710
  if (request->hasParam("brightness")) {
719
711
  auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
@@ -768,9 +760,9 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
768
760
  call.set_effect(effect);
769
761
  }
770
762
 
771
- this->schedule_([call]() mutable { call.perform(); });
763
+ this->defer([call]() mutable { call.perform(); });
772
764
  request->send(200);
773
- } else if (match.method == "turn_off") {
765
+ } else if (match.method_equals("turn_off")) {
774
766
  auto call = obj->turn_off();
775
767
  if (request->hasParam("transition")) {
776
768
  auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
@@ -778,7 +770,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
778
770
  call.set_transition_length(*transition * 1000);
779
771
  }
780
772
  }
781
- this->schedule_([call]() mutable { call.perform(); });
773
+ this->defer([call]() mutable { call.perform(); });
782
774
  request->send(200);
783
775
  } else {
784
776
  request->send(404);
@@ -805,12 +797,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
805
797
  for (auto const &option : obj->get_effects()) {
806
798
  opt.add(option->get_name());
807
799
  }
808
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
809
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
810
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
811
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
812
- }
813
- }
800
+ this->add_sorting_info_(root, obj);
814
801
  }
815
802
  });
816
803
  }
@@ -824,30 +811,26 @@ void WebServer::on_cover_update(cover::Cover *obj) {
824
811
  }
825
812
  void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
826
813
  for (cover::Cover *obj : App.get_covers()) {
827
- if (obj->get_object_id() != match.id)
814
+ if (!match.id_equals(obj->get_object_id()))
828
815
  continue;
829
816
 
830
- if (request->method() == HTTP_GET && match.method.empty()) {
831
- auto detail = DETAIL_STATE;
832
- auto *param = request->getParam("detail");
833
- if (param && param->value() == "all") {
834
- detail = DETAIL_ALL;
835
- }
817
+ if (request->method() == HTTP_GET && match.method_empty()) {
818
+ auto detail = get_request_detail(request);
836
819
  std::string data = this->cover_json(obj, detail);
837
820
  request->send(200, "application/json", data.c_str());
838
821
  return;
839
822
  }
840
823
 
841
824
  auto call = obj->make_call();
842
- if (match.method == "open") {
825
+ if (match.method_equals("open")) {
843
826
  call.set_command_open();
844
- } else if (match.method == "close") {
827
+ } else if (match.method_equals("close")) {
845
828
  call.set_command_close();
846
- } else if (match.method == "stop") {
829
+ } else if (match.method_equals("stop")) {
847
830
  call.set_command_stop();
848
- } else if (match.method == "toggle") {
831
+ } else if (match.method_equals("toggle")) {
849
832
  call.set_command_toggle();
850
- } else if (match.method != "set") {
833
+ } else if (!match.method_equals("set")) {
851
834
  request->send(404);
852
835
  return;
853
836
  }
@@ -872,7 +855,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
872
855
  }
873
856
  }
874
857
 
875
- this->schedule_([call]() mutable { call.perform(); });
858
+ this->defer([call]() mutable { call.perform(); });
876
859
  request->send(200);
877
860
  return;
878
861
  }
@@ -895,12 +878,7 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
895
878
  if (obj->get_traits().get_supports_tilt())
896
879
  root["tilt"] = obj->tilt;
897
880
  if (start_config == DETAIL_ALL) {
898
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
899
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
900
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
901
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
902
- }
903
- }
881
+ this->add_sorting_info_(root, obj);
904
882
  }
905
883
  });
906
884
  }
@@ -914,20 +892,16 @@ void WebServer::on_number_update(number::Number *obj, float state) {
914
892
  }
915
893
  void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
916
894
  for (auto *obj : App.get_numbers()) {
917
- if (obj->get_object_id() != match.id)
895
+ if (!match.id_equals(obj->get_object_id()))
918
896
  continue;
919
897
 
920
- if (request->method() == HTTP_GET && match.method.empty()) {
921
- auto detail = DETAIL_STATE;
922
- auto *param = request->getParam("detail");
923
- if (param && param->value() == "all") {
924
- detail = DETAIL_ALL;
925
- }
898
+ if (request->method() == HTTP_GET && match.method_empty()) {
899
+ auto detail = get_request_detail(request);
926
900
  std::string data = this->number_json(obj, obj->state, detail);
927
901
  request->send(200, "application/json", data.c_str());
928
902
  return;
929
903
  }
930
- if (match.method != "set") {
904
+ if (!match.method_equals("set")) {
931
905
  request->send(404);
932
906
  return;
933
907
  }
@@ -939,7 +913,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
939
913
  call.set_value(*value);
940
914
  }
941
915
 
942
- this->schedule_([call]() mutable { call.perform(); });
916
+ this->defer([call]() mutable { call.perform(); });
943
917
  request->send(200);
944
918
  return;
945
919
  }
@@ -965,12 +939,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
965
939
  root["mode"] = (int) obj->traits.get_mode();
966
940
  if (!obj->traits.get_unit_of_measurement().empty())
967
941
  root["uom"] = obj->traits.get_unit_of_measurement();
968
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
969
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
970
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
971
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
972
- }
973
- }
942
+ this->add_sorting_info_(root, obj);
974
943
  }
975
944
  if (std::isnan(value)) {
976
945
  root["value"] = "\"NaN\"";
@@ -994,19 +963,15 @@ void WebServer::on_date_update(datetime::DateEntity *obj) {
994
963
  }
995
964
  void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
996
965
  for (auto *obj : App.get_dates()) {
997
- if (obj->get_object_id() != match.id)
966
+ if (!match.id_equals(obj->get_object_id()))
998
967
  continue;
999
- if (request->method() == HTTP_GET && match.method.empty()) {
1000
- auto detail = DETAIL_STATE;
1001
- auto *param = request->getParam("detail");
1002
- if (param && param->value() == "all") {
1003
- detail = DETAIL_ALL;
1004
- }
968
+ if (request->method() == HTTP_GET && match.method_empty()) {
969
+ auto detail = get_request_detail(request);
1005
970
  std::string data = this->date_json(obj, detail);
1006
971
  request->send(200, "application/json", data.c_str());
1007
972
  return;
1008
973
  }
1009
- if (match.method != "set") {
974
+ if (!match.method_equals("set")) {
1010
975
  request->send(404);
1011
976
  return;
1012
977
  }
@@ -1023,7 +988,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
1023
988
  call.set_date(value);
1024
989
  }
1025
990
 
1026
- this->schedule_([call]() mutable { call.perform(); });
991
+ this->defer([call]() mutable { call.perform(); });
1027
992
  request->send(200);
1028
993
  return;
1029
994
  }
@@ -1043,12 +1008,7 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
1043
1008
  root["value"] = value;
1044
1009
  root["state"] = value;
1045
1010
  if (start_config == DETAIL_ALL) {
1046
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1047
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1048
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1049
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1050
- }
1051
- }
1011
+ this->add_sorting_info_(root, obj);
1052
1012
  }
1053
1013
  });
1054
1014
  }
@@ -1062,19 +1022,15 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) {
1062
1022
  }
1063
1023
  void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1064
1024
  for (auto *obj : App.get_times()) {
1065
- if (obj->get_object_id() != match.id)
1025
+ if (!match.id_equals(obj->get_object_id()))
1066
1026
  continue;
1067
- if (request->method() == HTTP_GET && match.method.empty()) {
1068
- auto detail = DETAIL_STATE;
1069
- auto *param = request->getParam("detail");
1070
- if (param && param->value() == "all") {
1071
- detail = DETAIL_ALL;
1072
- }
1027
+ if (request->method() == HTTP_GET && match.method_empty()) {
1028
+ auto detail = get_request_detail(request);
1073
1029
  std::string data = this->time_json(obj, detail);
1074
1030
  request->send(200, "application/json", data.c_str());
1075
1031
  return;
1076
1032
  }
1077
- if (match.method != "set") {
1033
+ if (!match.method_equals("set")) {
1078
1034
  request->send(404);
1079
1035
  return;
1080
1036
  }
@@ -1091,7 +1047,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
1091
1047
  call.set_time(value);
1092
1048
  }
1093
1049
 
1094
- this->schedule_([call]() mutable { call.perform(); });
1050
+ this->defer([call]() mutable { call.perform(); });
1095
1051
  request->send(200);
1096
1052
  return;
1097
1053
  }
@@ -1110,12 +1066,7 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
1110
1066
  root["value"] = value;
1111
1067
  root["state"] = value;
1112
1068
  if (start_config == DETAIL_ALL) {
1113
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1114
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1115
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1116
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1117
- }
1118
- }
1069
+ this->add_sorting_info_(root, obj);
1119
1070
  }
1120
1071
  });
1121
1072
  }
@@ -1129,19 +1080,15 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
1129
1080
  }
1130
1081
  void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1131
1082
  for (auto *obj : App.get_datetimes()) {
1132
- if (obj->get_object_id() != match.id)
1083
+ if (!match.id_equals(obj->get_object_id()))
1133
1084
  continue;
1134
- if (request->method() == HTTP_GET && match.method.empty()) {
1135
- auto detail = DETAIL_STATE;
1136
- auto *param = request->getParam("detail");
1137
- if (param && param->value() == "all") {
1138
- detail = DETAIL_ALL;
1139
- }
1085
+ if (request->method() == HTTP_GET && match.method_empty()) {
1086
+ auto detail = get_request_detail(request);
1140
1087
  std::string data = this->datetime_json(obj, detail);
1141
1088
  request->send(200, "application/json", data.c_str());
1142
1089
  return;
1143
1090
  }
1144
- if (match.method != "set") {
1091
+ if (!match.method_equals("set")) {
1145
1092
  request->send(404);
1146
1093
  return;
1147
1094
  }
@@ -1158,7 +1105,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
1158
1105
  call.set_datetime(value);
1159
1106
  }
1160
1107
 
1161
- this->schedule_([call]() mutable { call.perform(); });
1108
+ this->defer([call]() mutable { call.perform(); });
1162
1109
  request->send(200);
1163
1110
  return;
1164
1111
  }
@@ -1178,12 +1125,7 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
1178
1125
  root["value"] = value;
1179
1126
  root["state"] = value;
1180
1127
  if (start_config == DETAIL_ALL) {
1181
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1182
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1183
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1184
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1185
- }
1186
- }
1128
+ this->add_sorting_info_(root, obj);
1187
1129
  }
1188
1130
  });
1189
1131
  }
@@ -1197,20 +1139,16 @@ void WebServer::on_text_update(text::Text *obj, const std::string &state) {
1197
1139
  }
1198
1140
  void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1199
1141
  for (auto *obj : App.get_texts()) {
1200
- if (obj->get_object_id() != match.id)
1142
+ if (!match.id_equals(obj->get_object_id()))
1201
1143
  continue;
1202
1144
 
1203
- if (request->method() == HTTP_GET && match.method.empty()) {
1204
- auto detail = DETAIL_STATE;
1205
- auto *param = request->getParam("detail");
1206
- if (param && param->value() == "all") {
1207
- detail = DETAIL_ALL;
1208
- }
1145
+ if (request->method() == HTTP_GET && match.method_empty()) {
1146
+ auto detail = get_request_detail(request);
1209
1147
  std::string data = this->text_json(obj, obj->state, detail);
1210
1148
  request->send(200, "application/json", data.c_str());
1211
1149
  return;
1212
1150
  }
1213
- if (match.method != "set") {
1151
+ if (!match.method_equals("set")) {
1214
1152
  request->send(404);
1215
1153
  return;
1216
1154
  }
@@ -1248,12 +1186,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
1248
1186
  root["value"] = value;
1249
1187
  if (start_config == DETAIL_ALL) {
1250
1188
  root["mode"] = (int) obj->traits.get_mode();
1251
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1252
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1253
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1254
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1255
- }
1256
- }
1189
+ this->add_sorting_info_(root, obj);
1257
1190
  }
1258
1191
  });
1259
1192
  }
@@ -1267,21 +1200,17 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state,
1267
1200
  }
1268
1201
  void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1269
1202
  for (auto *obj : App.get_selects()) {
1270
- if (obj->get_object_id() != match.id)
1203
+ if (!match.id_equals(obj->get_object_id()))
1271
1204
  continue;
1272
1205
 
1273
- if (request->method() == HTTP_GET && match.method.empty()) {
1274
- auto detail = DETAIL_STATE;
1275
- auto *param = request->getParam("detail");
1276
- if (param && param->value() == "all") {
1277
- detail = DETAIL_ALL;
1278
- }
1206
+ if (request->method() == HTTP_GET && match.method_empty()) {
1207
+ auto detail = get_request_detail(request);
1279
1208
  std::string data = this->select_json(obj, obj->state, detail);
1280
1209
  request->send(200, "application/json", data.c_str());
1281
1210
  return;
1282
1211
  }
1283
1212
 
1284
- if (match.method != "set") {
1213
+ if (!match.method_equals("set")) {
1285
1214
  request->send(404);
1286
1215
  return;
1287
1216
  }
@@ -1293,7 +1222,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
1293
1222
  call.set_option(option.c_str()); // NOLINT
1294
1223
  }
1295
1224
 
1296
- this->schedule_([call]() mutable { call.perform(); });
1225
+ this->defer([call]() mutable { call.perform(); });
1297
1226
  request->send(200);
1298
1227
  return;
1299
1228
  }
@@ -1313,12 +1242,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
1313
1242
  for (auto &option : obj->traits.get_options()) {
1314
1243
  opt.add(option);
1315
1244
  }
1316
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1317
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1318
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1319
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1320
- }
1321
- }
1245
+ this->add_sorting_info_(root, obj);
1322
1246
  }
1323
1247
  });
1324
1248
  }
@@ -1335,21 +1259,17 @@ void WebServer::on_climate_update(climate::Climate *obj) {
1335
1259
  }
1336
1260
  void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1337
1261
  for (auto *obj : App.get_climates()) {
1338
- if (obj->get_object_id() != match.id)
1262
+ if (!match.id_equals(obj->get_object_id()))
1339
1263
  continue;
1340
1264
 
1341
- if (request->method() == HTTP_GET && match.method.empty()) {
1342
- auto detail = DETAIL_STATE;
1343
- auto *param = request->getParam("detail");
1344
- if (param && param->value() == "all") {
1345
- detail = DETAIL_ALL;
1346
- }
1265
+ if (request->method() == HTTP_GET && match.method_empty()) {
1266
+ auto detail = get_request_detail(request);
1347
1267
  std::string data = this->climate_json(obj, detail);
1348
1268
  request->send(200, "application/json", data.c_str());
1349
1269
  return;
1350
1270
  }
1351
1271
 
1352
- if (match.method != "set") {
1272
+ if (!match.method_equals("set")) {
1353
1273
  request->send(404);
1354
1274
  return;
1355
1275
  }
@@ -1389,7 +1309,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
1389
1309
  call.set_target_temperature(*target_temperature);
1390
1310
  }
1391
1311
 
1392
- this->schedule_([call]() mutable { call.perform(); });
1312
+ this->defer([call]() mutable { call.perform(); });
1393
1313
  request->send(200);
1394
1314
  return;
1395
1315
  }
@@ -1439,12 +1359,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
1439
1359
  for (auto const &custom_preset : traits.get_supported_custom_presets())
1440
1360
  opt.add(custom_preset);
1441
1361
  }
1442
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1443
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1444
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1445
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1446
- }
1447
- }
1362
+ this->add_sorting_info_(root, obj);
1448
1363
  }
1449
1364
 
1450
1365
  bool has_state = false;
@@ -1503,25 +1418,21 @@ void WebServer::on_lock_update(lock::Lock *obj) {
1503
1418
  }
1504
1419
  void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1505
1420
  for (lock::Lock *obj : App.get_locks()) {
1506
- if (obj->get_object_id() != match.id)
1421
+ if (!match.id_equals(obj->get_object_id()))
1507
1422
  continue;
1508
1423
 
1509
- if (request->method() == HTTP_GET && match.method.empty()) {
1510
- auto detail = DETAIL_STATE;
1511
- auto *param = request->getParam("detail");
1512
- if (param && param->value() == "all") {
1513
- detail = DETAIL_ALL;
1514
- }
1424
+ if (request->method() == HTTP_GET && match.method_empty()) {
1425
+ auto detail = get_request_detail(request);
1515
1426
  std::string data = this->lock_json(obj, obj->state, detail);
1516
1427
  request->send(200, "application/json", data.c_str());
1517
- } else if (match.method == "lock") {
1518
- this->schedule_([obj]() { obj->lock(); });
1428
+ } else if (match.method_equals("lock")) {
1429
+ this->defer([obj]() { obj->lock(); });
1519
1430
  request->send(200);
1520
- } else if (match.method == "unlock") {
1521
- this->schedule_([obj]() { obj->unlock(); });
1431
+ } else if (match.method_equals("unlock")) {
1432
+ this->defer([obj]() { obj->unlock(); });
1522
1433
  request->send(200);
1523
- } else if (match.method == "open") {
1524
- this->schedule_([obj]() { obj->open(); });
1434
+ } else if (match.method_equals("open")) {
1435
+ this->defer([obj]() { obj->open(); });
1525
1436
  request->send(200);
1526
1437
  } else {
1527
1438
  request->send(404);
@@ -1541,12 +1452,7 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
1541
1452
  set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
1542
1453
  start_config);
1543
1454
  if (start_config == DETAIL_ALL) {
1544
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1545
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1546
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1547
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1548
- }
1549
- }
1455
+ this->add_sorting_info_(root, obj);
1550
1456
  }
1551
1457
  });
1552
1458
  }
@@ -1560,30 +1466,26 @@ void WebServer::on_valve_update(valve::Valve *obj) {
1560
1466
  }
1561
1467
  void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1562
1468
  for (valve::Valve *obj : App.get_valves()) {
1563
- if (obj->get_object_id() != match.id)
1469
+ if (!match.id_equals(obj->get_object_id()))
1564
1470
  continue;
1565
1471
 
1566
- if (request->method() == HTTP_GET && match.method.empty()) {
1567
- auto detail = DETAIL_STATE;
1568
- auto *param = request->getParam("detail");
1569
- if (param && param->value() == "all") {
1570
- detail = DETAIL_ALL;
1571
- }
1472
+ if (request->method() == HTTP_GET && match.method_empty()) {
1473
+ auto detail = get_request_detail(request);
1572
1474
  std::string data = this->valve_json(obj, detail);
1573
1475
  request->send(200, "application/json", data.c_str());
1574
1476
  return;
1575
1477
  }
1576
1478
 
1577
1479
  auto call = obj->make_call();
1578
- if (match.method == "open") {
1480
+ if (match.method_equals("open")) {
1579
1481
  call.set_command_open();
1580
- } else if (match.method == "close") {
1482
+ } else if (match.method_equals("close")) {
1581
1483
  call.set_command_close();
1582
- } else if (match.method == "stop") {
1484
+ } else if (match.method_equals("stop")) {
1583
1485
  call.set_command_stop();
1584
- } else if (match.method == "toggle") {
1486
+ } else if (match.method_equals("toggle")) {
1585
1487
  call.set_command_toggle();
1586
- } else if (match.method != "set") {
1488
+ } else if (!match.method_equals("set")) {
1587
1489
  request->send(404);
1588
1490
  return;
1589
1491
  }
@@ -1601,7 +1503,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
1601
1503
  }
1602
1504
  }
1603
1505
 
1604
- this->schedule_([call]() mutable { call.perform(); });
1506
+ this->defer([call]() mutable { call.perform(); });
1605
1507
  request->send(200);
1606
1508
  return;
1607
1509
  }
@@ -1622,12 +1524,7 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
1622
1524
  if (obj->get_traits().get_supports_position())
1623
1525
  root["position"] = obj->position;
1624
1526
  if (start_config == DETAIL_ALL) {
1625
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1626
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1627
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1628
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1629
- }
1630
- }
1527
+ this->add_sorting_info_(root, obj);
1631
1528
  }
1632
1529
  });
1633
1530
  }
@@ -1641,15 +1538,11 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
1641
1538
  }
1642
1539
  void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1643
1540
  for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
1644
- if (obj->get_object_id() != match.id)
1541
+ if (!match.id_equals(obj->get_object_id()))
1645
1542
  continue;
1646
1543
 
1647
- if (request->method() == HTTP_GET && match.method.empty()) {
1648
- auto detail = DETAIL_STATE;
1649
- auto *param = request->getParam("detail");
1650
- if (param && param->value() == "all") {
1651
- detail = DETAIL_ALL;
1652
- }
1544
+ if (request->method() == HTTP_GET && match.method_empty()) {
1545
+ auto detail = get_request_detail(request);
1653
1546
  std::string data = this->alarm_control_panel_json(obj, obj->get_state(), detail);
1654
1547
  request->send(200, "application/json", data.c_str());
1655
1548
  return;
@@ -1660,22 +1553,22 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
1660
1553
  call.set_code(request->getParam("code")->value().c_str()); // NOLINT
1661
1554
  }
1662
1555
 
1663
- if (match.method == "disarm") {
1556
+ if (match.method_equals("disarm")) {
1664
1557
  call.disarm();
1665
- } else if (match.method == "arm_away") {
1558
+ } else if (match.method_equals("arm_away")) {
1666
1559
  call.arm_away();
1667
- } else if (match.method == "arm_home") {
1560
+ } else if (match.method_equals("arm_home")) {
1668
1561
  call.arm_home();
1669
- } else if (match.method == "arm_night") {
1562
+ } else if (match.method_equals("arm_night")) {
1670
1563
  call.arm_night();
1671
- } else if (match.method == "arm_vacation") {
1564
+ } else if (match.method_equals("arm_vacation")) {
1672
1565
  call.arm_vacation();
1673
1566
  } else {
1674
1567
  request->send(404);
1675
1568
  return;
1676
1569
  }
1677
1570
 
1678
- this->schedule_([call]() mutable { call.perform(); });
1571
+ this->defer([call]() mutable { call.perform(); });
1679
1572
  request->send(200);
1680
1573
  return;
1681
1574
  }
@@ -1699,12 +1592,7 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
1699
1592
  set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
1700
1593
  PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
1701
1594
  if (start_config == DETAIL_ALL) {
1702
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1703
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1704
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1705
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1706
- }
1707
- }
1595
+ this->add_sorting_info_(root, obj);
1708
1596
  }
1709
1597
  });
1710
1598
  }
@@ -1717,15 +1605,11 @@ void WebServer::on_event(event::Event *obj, const std::string &event_type) {
1717
1605
 
1718
1606
  void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1719
1607
  for (event::Event *obj : App.get_events()) {
1720
- if (obj->get_object_id() != match.id)
1608
+ if (!match.id_equals(obj->get_object_id()))
1721
1609
  continue;
1722
1610
 
1723
- if (request->method() == HTTP_GET && match.method.empty()) {
1724
- auto detail = DETAIL_STATE;
1725
- auto *param = request->getParam("detail");
1726
- if (param && param->value() == "all") {
1727
- detail = DETAIL_ALL;
1728
- }
1611
+ if (request->method() == HTTP_GET && match.method_empty()) {
1612
+ auto detail = get_request_detail(request);
1729
1613
  std::string data = this->event_json(obj, "", detail);
1730
1614
  request->send(200, "application/json", data.c_str());
1731
1615
  return;
@@ -1734,12 +1618,15 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa
1734
1618
  request->send(404);
1735
1619
  }
1736
1620
 
1621
+ static std::string get_event_type(event::Event *event) { return event->last_event_type ? *event->last_event_type : ""; }
1622
+
1737
1623
  std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) {
1738
- return web_server->event_json((event::Event *) (source), *(((event::Event *) (source))->last_event_type),
1739
- DETAIL_STATE);
1624
+ auto *event = static_cast<event::Event *>(source);
1625
+ return web_server->event_json(event, get_event_type(event), DETAIL_STATE);
1740
1626
  }
1741
1627
  std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) {
1742
- return web_server->event_json((event::Event *) (source), *(((event::Event *) (source))->last_event_type), DETAIL_ALL);
1628
+ auto *event = static_cast<event::Event *>(source);
1629
+ return web_server->event_json(event, get_event_type(event), DETAIL_ALL);
1743
1630
  }
1744
1631
  std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
1745
1632
  return json::build_json([this, obj, event_type, start_config](JsonObject root) {
@@ -1753,12 +1640,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
1753
1640
  event_types.add(event_type);
1754
1641
  }
1755
1642
  root["device_class"] = obj->get_device_class();
1756
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1757
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1758
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1759
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1760
- }
1761
- }
1643
+ this->add_sorting_info_(root, obj);
1762
1644
  }
1763
1645
  });
1764
1646
  }
@@ -1772,26 +1654,22 @@ void WebServer::on_update(update::UpdateEntity *obj) {
1772
1654
  }
1773
1655
  void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1774
1656
  for (update::UpdateEntity *obj : App.get_updates()) {
1775
- if (obj->get_object_id() != match.id)
1657
+ if (!match.id_equals(obj->get_object_id()))
1776
1658
  continue;
1777
1659
 
1778
- if (request->method() == HTTP_GET && match.method.empty()) {
1779
- auto detail = DETAIL_STATE;
1780
- auto *param = request->getParam("detail");
1781
- if (param && param->value() == "all") {
1782
- detail = DETAIL_ALL;
1783
- }
1660
+ if (request->method() == HTTP_GET && match.method_empty()) {
1661
+ auto detail = get_request_detail(request);
1784
1662
  std::string data = this->update_json(obj, detail);
1785
1663
  request->send(200, "application/json", data.c_str());
1786
1664
  return;
1787
1665
  }
1788
1666
 
1789
- if (match.method != "install") {
1667
+ if (!match.method_equals("install")) {
1790
1668
  request->send(404);
1791
1669
  return;
1792
1670
  }
1793
1671
 
1794
- this->schedule_([obj]() mutable { obj->perform(); });
1672
+ this->defer([obj]() mutable { obj->perform(); });
1795
1673
  request->send(200);
1796
1674
  return;
1797
1675
  }
@@ -1826,18 +1704,13 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
1826
1704
  root["title"] = obj->update_info.title;
1827
1705
  root["summary"] = obj->update_info.summary;
1828
1706
  root["release_url"] = obj->update_info.release_url;
1829
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1830
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1831
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1832
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1833
- }
1834
- }
1707
+ this->add_sorting_info_(root, obj);
1835
1708
  }
1836
1709
  });
1837
1710
  }
1838
1711
  #endif
1839
1712
 
1840
- bool WebServer::canHandle(AsyncWebServerRequest *request) {
1713
+ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
1841
1714
  if (request->url() == "/")
1842
1715
  return true;
1843
1716
 
@@ -1859,116 +1732,114 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
1859
1732
 
1860
1733
  #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1861
1734
  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1862
- #ifdef USE_ARDUINO
1863
- // Header needs to be added to interesting header list for it to not be
1864
- // nuked by the time we handle the request later.
1865
- // Only required in Arduino framework.
1866
- request->addInterestingHeader(HEADER_CORS_REQ_PNA);
1867
- #endif
1868
1735
  return true;
1869
1736
  }
1870
1737
  #endif
1871
1738
 
1872
- UrlMatch match = match_url(request->url().c_str(), true); // NOLINT
1739
+ // Store the URL to prevent temporary string destruction
1740
+ // request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF)
1741
+ // UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url()
1742
+ const auto &url = request->url();
1743
+ UrlMatch match = match_url(url.c_str(), url.length(), true);
1873
1744
  if (!match.valid)
1874
1745
  return false;
1875
1746
  #ifdef USE_SENSOR
1876
- if (request->method() == HTTP_GET && match.domain == "sensor")
1747
+ if (request->method() == HTTP_GET && match.domain_equals("sensor"))
1877
1748
  return true;
1878
1749
  #endif
1879
1750
 
1880
1751
  #ifdef USE_SWITCH
1881
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "switch")
1752
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("switch"))
1882
1753
  return true;
1883
1754
  #endif
1884
1755
 
1885
1756
  #ifdef USE_BUTTON
1886
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button")
1757
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("button"))
1887
1758
  return true;
1888
1759
  #endif
1889
1760
 
1890
1761
  #ifdef USE_BINARY_SENSOR
1891
- if (request->method() == HTTP_GET && match.domain == "binary_sensor")
1762
+ if (request->method() == HTTP_GET && match.domain_equals("binary_sensor"))
1892
1763
  return true;
1893
1764
  #endif
1894
1765
 
1895
1766
  #ifdef USE_FAN
1896
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "fan")
1767
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("fan"))
1897
1768
  return true;
1898
1769
  #endif
1899
1770
 
1900
1771
  #ifdef USE_LIGHT
1901
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "light")
1772
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("light"))
1902
1773
  return true;
1903
1774
  #endif
1904
1775
 
1905
1776
  #ifdef USE_TEXT_SENSOR
1906
- if (request->method() == HTTP_GET && match.domain == "text_sensor")
1777
+ if (request->method() == HTTP_GET && match.domain_equals("text_sensor"))
1907
1778
  return true;
1908
1779
  #endif
1909
1780
 
1910
1781
  #ifdef USE_COVER
1911
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "cover")
1782
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("cover"))
1912
1783
  return true;
1913
1784
  #endif
1914
1785
 
1915
1786
  #ifdef USE_NUMBER
1916
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number")
1787
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("number"))
1917
1788
  return true;
1918
1789
  #endif
1919
1790
 
1920
1791
  #ifdef USE_DATETIME_DATE
1921
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date")
1792
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("date"))
1922
1793
  return true;
1923
1794
  #endif
1924
1795
 
1925
1796
  #ifdef USE_DATETIME_TIME
1926
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "time")
1797
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("time"))
1927
1798
  return true;
1928
1799
  #endif
1929
1800
 
1930
1801
  #ifdef USE_DATETIME_DATETIME
1931
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime")
1802
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("datetime"))
1932
1803
  return true;
1933
1804
  #endif
1934
1805
 
1935
1806
  #ifdef USE_TEXT
1936
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
1807
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("text"))
1937
1808
  return true;
1938
1809
  #endif
1939
1810
 
1940
1811
  #ifdef USE_SELECT
1941
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select")
1812
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("select"))
1942
1813
  return true;
1943
1814
  #endif
1944
1815
 
1945
1816
  #ifdef USE_CLIMATE
1946
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
1817
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("climate"))
1947
1818
  return true;
1948
1819
  #endif
1949
1820
 
1950
1821
  #ifdef USE_LOCK
1951
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
1822
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("lock"))
1952
1823
  return true;
1953
1824
  #endif
1954
1825
 
1955
1826
  #ifdef USE_VALVE
1956
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "valve")
1827
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("valve"))
1957
1828
  return true;
1958
1829
  #endif
1959
1830
 
1960
1831
  #ifdef USE_ALARM_CONTROL_PANEL
1961
- if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain == "alarm_control_panel")
1832
+ if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain_equals("alarm_control_panel"))
1962
1833
  return true;
1963
1834
  #endif
1964
1835
 
1965
1836
  #ifdef USE_EVENT
1966
- if (request->method() == HTTP_GET && match.domain == "event")
1837
+ if (request->method() == HTTP_GET && match.domain_equals("event"))
1967
1838
  return true;
1968
1839
  #endif
1969
1840
 
1970
1841
  #ifdef USE_UPDATE
1971
- if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update")
1842
+ if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("update"))
1972
1843
  return true;
1973
1844
  #endif
1974
1845
 
@@ -2008,114 +1879,117 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2008
1879
  }
2009
1880
  #endif
2010
1881
 
2011
- UrlMatch match = match_url(request->url().c_str()); // NOLINT
1882
+ // See comment in canHandle() for why we store the URL reference
1883
+ const auto &url = request->url();
1884
+ UrlMatch match = match_url(url.c_str(), url.length(), false);
1885
+
2012
1886
  #ifdef USE_SENSOR
2013
- if (match.domain == "sensor") {
1887
+ if (match.domain_equals("sensor")) {
2014
1888
  this->handle_sensor_request(request, match);
2015
1889
  return;
2016
1890
  }
2017
1891
  #endif
2018
1892
 
2019
1893
  #ifdef USE_SWITCH
2020
- if (match.domain == "switch") {
1894
+ if (match.domain_equals("switch")) {
2021
1895
  this->handle_switch_request(request, match);
2022
1896
  return;
2023
1897
  }
2024
1898
  #endif
2025
1899
 
2026
1900
  #ifdef USE_BUTTON
2027
- if (match.domain == "button") {
1901
+ if (match.domain_equals("button")) {
2028
1902
  this->handle_button_request(request, match);
2029
1903
  return;
2030
1904
  }
2031
1905
  #endif
2032
1906
 
2033
1907
  #ifdef USE_BINARY_SENSOR
2034
- if (match.domain == "binary_sensor") {
1908
+ if (match.domain_equals("binary_sensor")) {
2035
1909
  this->handle_binary_sensor_request(request, match);
2036
1910
  return;
2037
1911
  }
2038
1912
  #endif
2039
1913
 
2040
1914
  #ifdef USE_FAN
2041
- if (match.domain == "fan") {
1915
+ if (match.domain_equals("fan")) {
2042
1916
  this->handle_fan_request(request, match);
2043
1917
  return;
2044
1918
  }
2045
1919
  #endif
2046
1920
 
2047
1921
  #ifdef USE_LIGHT
2048
- if (match.domain == "light") {
1922
+ if (match.domain_equals("light")) {
2049
1923
  this->handle_light_request(request, match);
2050
1924
  return;
2051
1925
  }
2052
1926
  #endif
2053
1927
 
2054
1928
  #ifdef USE_TEXT_SENSOR
2055
- if (match.domain == "text_sensor") {
1929
+ if (match.domain_equals("text_sensor")) {
2056
1930
  this->handle_text_sensor_request(request, match);
2057
1931
  return;
2058
1932
  }
2059
1933
  #endif
2060
1934
 
2061
1935
  #ifdef USE_COVER
2062
- if (match.domain == "cover") {
1936
+ if (match.domain_equals("cover")) {
2063
1937
  this->handle_cover_request(request, match);
2064
1938
  return;
2065
1939
  }
2066
1940
  #endif
2067
1941
 
2068
1942
  #ifdef USE_NUMBER
2069
- if (match.domain == "number") {
1943
+ if (match.domain_equals("number")) {
2070
1944
  this->handle_number_request(request, match);
2071
1945
  return;
2072
1946
  }
2073
1947
  #endif
2074
1948
 
2075
1949
  #ifdef USE_DATETIME_DATE
2076
- if (match.domain == "date") {
1950
+ if (match.domain_equals("date")) {
2077
1951
  this->handle_date_request(request, match);
2078
1952
  return;
2079
1953
  }
2080
1954
  #endif
2081
1955
 
2082
1956
  #ifdef USE_DATETIME_TIME
2083
- if (match.domain == "time") {
1957
+ if (match.domain_equals("time")) {
2084
1958
  this->handle_time_request(request, match);
2085
1959
  return;
2086
1960
  }
2087
1961
  #endif
2088
1962
 
2089
1963
  #ifdef USE_DATETIME_DATETIME
2090
- if (match.domain == "datetime") {
1964
+ if (match.domain_equals("datetime")) {
2091
1965
  this->handle_datetime_request(request, match);
2092
1966
  return;
2093
1967
  }
2094
1968
  #endif
2095
1969
 
2096
1970
  #ifdef USE_TEXT
2097
- if (match.domain == "text") {
1971
+ if (match.domain_equals("text")) {
2098
1972
  this->handle_text_request(request, match);
2099
1973
  return;
2100
1974
  }
2101
1975
  #endif
2102
1976
 
2103
1977
  #ifdef USE_SELECT
2104
- if (match.domain == "select") {
1978
+ if (match.domain_equals("select")) {
2105
1979
  this->handle_select_request(request, match);
2106
1980
  return;
2107
1981
  }
2108
1982
  #endif
2109
1983
 
2110
1984
  #ifdef USE_CLIMATE
2111
- if (match.domain == "climate") {
1985
+ if (match.domain_equals("climate")) {
2112
1986
  this->handle_climate_request(request, match);
2113
1987
  return;
2114
1988
  }
2115
1989
  #endif
2116
1990
 
2117
1991
  #ifdef USE_LOCK
2118
- if (match.domain == "lock") {
1992
+ if (match.domain_equals("lock")) {
2119
1993
  this->handle_lock_request(request, match);
2120
1994
 
2121
1995
  return;
@@ -2123,14 +1997,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2123
1997
  #endif
2124
1998
 
2125
1999
  #ifdef USE_VALVE
2126
- if (match.domain == "valve") {
2000
+ if (match.domain_equals("valve")) {
2127
2001
  this->handle_valve_request(request, match);
2128
2002
  return;
2129
2003
  }
2130
2004
  #endif
2131
2005
 
2132
2006
  #ifdef USE_ALARM_CONTROL_PANEL
2133
- if (match.domain == "alarm_control_panel") {
2007
+ if (match.domain_equals("alarm_control_panel")) {
2134
2008
  this->handle_alarm_control_panel_request(request, match);
2135
2009
 
2136
2010
  return;
@@ -2138,15 +2012,31 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2138
2012
  #endif
2139
2013
 
2140
2014
  #ifdef USE_UPDATE
2141
- if (match.domain == "update") {
2015
+ if (match.domain_equals("update")) {
2142
2016
  this->handle_update_request(request, match);
2143
2017
  return;
2144
2018
  }
2145
2019
  #endif
2020
+
2021
+ // No matching handler found - send 404
2022
+ ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str());
2023
+ request->send(404, "text/plain", "Not Found");
2146
2024
  }
2147
2025
 
2148
- bool WebServer::isRequestHandlerTrivial() { return false; }
2026
+ bool WebServer::isRequestHandlerTrivial() const { return false; }
2149
2027
 
2028
+ void WebServer::add_sorting_info_(JsonObject &root, EntityBase *entity) {
2029
+ #ifdef USE_WEBSERVER_SORTING
2030
+ if (this->sorting_entitys_.find(entity) != this->sorting_entitys_.end()) {
2031
+ root["sorting_weight"] = this->sorting_entitys_[entity].weight;
2032
+ if (this->sorting_groups_.find(this->sorting_entitys_[entity].group_id) != this->sorting_groups_.end()) {
2033
+ root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[entity].group_id].name;
2034
+ }
2035
+ }
2036
+ #endif
2037
+ }
2038
+
2039
+ #ifdef USE_WEBSERVER_SORTING
2150
2040
  void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) {
2151
2041
  this->sorting_entitys_[entity] = SortingComponents{weight, group};
2152
2042
  }
@@ -2154,16 +2044,7 @@ void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t gro
2154
2044
  void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) {
2155
2045
  this->sorting_groups_[group_id] = SortingGroup{group_name, weight};
2156
2046
  }
2157
-
2158
- void WebServer::schedule_(std::function<void()> &&f) {
2159
- #ifdef USE_ESP32
2160
- xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY);
2161
- to_schedule_.push_back(std::move(f));
2162
- xSemaphoreGive(this->to_schedule_lock_);
2163
- #else
2164
- this->defer(std::move(f));
2165
2047
  #endif
2166
- }
2167
2048
 
2168
2049
  } // namespace web_server
2169
2050
  } // namespace esphome