esphome 2025.6.3__py3-none-any.whl → 2025.7.0b2__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 (606) hide show
  1. esphome/__main__.py +1 -3
  2. esphome/codegen.py +2 -0
  3. esphome/components/ac_dimmer/ac_dimmer.cpp +6 -6
  4. esphome/components/adc/__init__.py +25 -1
  5. esphome/components/adc/adc_sensor.h +11 -11
  6. esphome/components/adc/adc_sensor_common.cpp +1 -1
  7. esphome/components/adc/adc_sensor_esp32.cpp +16 -8
  8. esphome/components/ade7880/ade7880.h +0 -2
  9. esphome/components/ads1115/ads1115.h +0 -1
  10. esphome/components/ads1118/ads1118.h +0 -1
  11. esphome/components/ags10/ags10.h +0 -2
  12. esphome/components/aic3204/aic3204.h +0 -1
  13. esphome/components/alarm_control_panel/__init__.py +5 -2
  14. esphome/components/alpha3/alpha3.h +0 -1
  15. esphome/components/am43/cover/am43_cover.h +0 -1
  16. esphome/components/am43/sensor/am43_sensor.h +0 -1
  17. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +0 -2
  18. esphome/components/anova/anova.cpp +5 -1
  19. esphome/components/anova/anova.h +0 -1
  20. esphome/components/apds9960/apds9960.cpp +1 -1
  21. esphome/components/api/__init__.py +57 -21
  22. esphome/components/api/api_connection.cpp +344 -539
  23. esphome/components/api/api_connection.h +224 -141
  24. esphome/components/api/api_frame_helper.cpp +91 -127
  25. esphome/components/api/api_frame_helper.h +64 -54
  26. esphome/components/api/api_pb2.cpp +1837 -9044
  27. esphome/components/api/api_pb2.h +532 -685
  28. esphome/components/api/api_pb2_dump.cpp +4432 -0
  29. esphome/components/api/api_pb2_service.cpp +184 -425
  30. esphome/components/api/api_pb2_service.h +13 -6
  31. esphome/components/api/api_server.cpp +131 -167
  32. esphome/components/api/api_server.h +38 -10
  33. esphome/components/api/client.py +8 -2
  34. esphome/components/api/custom_api_device.h +8 -0
  35. esphome/components/api/list_entities.cpp +37 -104
  36. esphome/components/api/list_entities.h +33 -23
  37. esphome/components/api/proto.h +532 -26
  38. esphome/components/api/subscribe_state.cpp +23 -29
  39. esphome/components/api/subscribe_state.h +26 -19
  40. esphome/components/api/user_services.h +2 -0
  41. esphome/components/as5600/as5600.h +0 -1
  42. esphome/components/async_tcp/__init__.py +14 -5
  43. esphome/components/atc_mithermometer/atc_mithermometer.h +0 -1
  44. esphome/components/atm90e32/atm90e32.cpp +2 -1
  45. esphome/components/audio/audio_decoder.cpp +1 -1
  46. esphome/components/audio/audio_transfer_buffer.cpp +2 -2
  47. esphome/components/b_parasite/b_parasite.h +0 -1
  48. esphome/components/bedjet/bedjet_hub.cpp +5 -1
  49. esphome/components/bedjet/climate/bedjet_climate.cpp +5 -1
  50. esphome/components/beken_spi_led_strip/led_strip.cpp +4 -2
  51. esphome/components/bh1750/bh1750.cpp +5 -5
  52. esphome/components/binary_sensor/__init__.py +82 -5
  53. esphome/components/binary_sensor/automation.h +19 -1
  54. esphome/components/binary_sensor/binary_sensor.cpp +12 -30
  55. esphome/components/binary_sensor/binary_sensor.h +11 -25
  56. esphome/components/binary_sensor/filter.cpp +29 -24
  57. esphome/components/binary_sensor/filter.h +20 -10
  58. esphome/components/ble_client/output/ble_binary_output.h +0 -1
  59. esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +5 -1
  60. esphome/components/ble_client/sensor/ble_rssi_sensor.h +0 -1
  61. esphome/components/ble_client/sensor/ble_sensor.cpp +5 -1
  62. esphome/components/ble_client/sensor/ble_sensor.h +0 -1
  63. esphome/components/ble_client/switch/ble_switch.h +0 -1
  64. esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +5 -1
  65. esphome/components/ble_client/text_sensor/ble_text_sensor.h +0 -1
  66. esphome/components/ble_presence/ble_presence_device.h +0 -1
  67. esphome/components/ble_rssi/ble_rssi_sensor.h +0 -1
  68. esphome/components/ble_scanner/ble_scanner.h +0 -1
  69. esphome/components/bluetooth_proxy/bluetooth_connection.h +9 -2
  70. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +16 -6
  71. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -2
  72. esphome/components/bme680/sensor.py +1 -1
  73. esphome/components/bmp581/bmp581.h +0 -2
  74. esphome/components/button/__init__.py +5 -2
  75. esphome/components/camera/__init__.py +1 -0
  76. esphome/components/camera/camera.cpp +22 -0
  77. esphome/components/camera/camera.h +80 -0
  78. esphome/components/canbus/__init__.py +1 -0
  79. esphome/components/cap1188/cap1188.h +0 -1
  80. esphome/components/captive_portal/__init__.py +12 -2
  81. esphome/components/captive_portal/captive_portal.cpp +12 -2
  82. esphome/components/captive_portal/captive_portal.h +5 -2
  83. esphome/components/ccs811/ccs811.h +0 -2
  84. esphome/components/climate/__init__.py +5 -2
  85. esphome/components/cm1106/sensor.py +2 -2
  86. esphome/components/const/__init__.py +2 -0
  87. esphome/components/copy/binary_sensor/copy_binary_sensor.h +0 -1
  88. esphome/components/copy/button/copy_button.h +0 -1
  89. esphome/components/copy/cover/copy_cover.h +0 -1
  90. esphome/components/copy/fan/copy_fan.h +0 -1
  91. esphome/components/copy/lock/copy_lock.h +0 -1
  92. esphome/components/copy/number/copy_number.h +0 -1
  93. esphome/components/copy/select/copy_select.h +0 -1
  94. esphome/components/copy/sensor/copy_sensor.h +0 -1
  95. esphome/components/copy/switch/copy_switch.h +0 -1
  96. esphome/components/copy/text/copy_text.h +0 -1
  97. esphome/components/copy/text_sensor/copy_text_sensor.h +0 -1
  98. esphome/components/cover/__init__.py +5 -2
  99. esphome/components/cs5460a/cs5460a.h +0 -1
  100. esphome/components/datetime/__init__.py +4 -2
  101. esphome/components/debug/__init__.py +20 -0
  102. esphome/components/debug/debug_esp32.cpp +2 -0
  103. esphome/components/deep_sleep/__init__.py +43 -9
  104. esphome/components/demo/__init__.py +2 -2
  105. esphome/components/display/display.cpp +4 -3
  106. esphome/components/display/display.h +0 -2
  107. esphome/components/display/display_buffer.cpp +1 -1
  108. esphome/components/ds2484/__init__.py +1 -0
  109. esphome/components/ds2484/ds2484.cpp +209 -0
  110. esphome/components/ds2484/ds2484.h +43 -0
  111. esphome/components/ds2484/one_wire.py +37 -0
  112. esphome/components/duty_time/duty_time_sensor.h +0 -1
  113. esphome/components/ens160_base/ens160_base.h +0 -1
  114. esphome/components/es7210/es7210.h +0 -1
  115. esphome/components/es7243e/es7243e.h +0 -1
  116. esphome/components/es8156/es8156.h +0 -1
  117. esphome/components/es8311/es8311.h +0 -1
  118. esphome/components/es8388/es8388.h +0 -1
  119. esphome/components/esp32/__init__.py +103 -135
  120. esphome/components/esp32/core.cpp +0 -4
  121. esphome/components/esp32/gpio.h +1 -1
  122. esphome/components/esp32/helpers.cpp +69 -0
  123. esphome/components/esp32_ble/ble.cpp +5 -6
  124. esphome/components/esp32_ble/ble.h +29 -14
  125. esphome/components/esp32_ble/ble_event.h +6 -6
  126. esphome/components/esp32_ble_client/ble_client_base.cpp +21 -6
  127. esphome/components/esp32_ble_client/ble_client_base.h +24 -9
  128. esphome/components/esp32_ble_tracker/__init__.py +2 -8
  129. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +5 -5
  130. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +11 -7
  131. esphome/components/esp32_camera/__init__.py +112 -98
  132. esphome/components/esp32_camera/esp32_camera.cpp +41 -31
  133. esphome/components/esp32_camera/esp32_camera.h +35 -30
  134. esphome/components/esp32_camera_web_server/__init__.py +2 -1
  135. esphome/components/esp32_camera_web_server/camera_web_server.cpp +8 -8
  136. esphome/components/esp32_camera_web_server/camera_web_server.h +3 -3
  137. esphome/components/esp32_hall/sensor.py +2 -21
  138. esphome/components/esp32_hosted/__init__.py +101 -0
  139. esphome/components/esp32_hosted/esp32_hosted.py.script +12 -0
  140. esphome/components/esp32_improv/esp32_improv_component.cpp +3 -0
  141. esphome/components/esp32_rmt/__init__.py +0 -58
  142. esphome/components/esp32_rmt_led_strip/led_strip.cpp +77 -63
  143. esphome/components/esp32_rmt_led_strip/led_strip.h +11 -17
  144. esphome/components/esp32_rmt_led_strip/light.py +14 -76
  145. esphome/components/esp32_touch/esp32_touch.h +174 -28
  146. esphome/components/esp32_touch/esp32_touch_common.cpp +162 -0
  147. esphome/components/esp32_touch/esp32_touch_v1.cpp +240 -0
  148. esphome/components/esp32_touch/esp32_touch_v2.cpp +397 -0
  149. esphome/components/esp8266/__init__.py +2 -0
  150. esphome/components/esp8266/gpio.cpp +10 -10
  151. esphome/components/esp8266/helpers.cpp +31 -0
  152. esphome/components/esphome/ota/__init__.py +1 -0
  153. esphome/components/esphome/ota/ota_esphome.cpp +24 -19
  154. esphome/components/ethernet/__init__.py +42 -23
  155. esphome/components/ethernet/esp_eth_phy_jl1101.c +0 -16
  156. esphome/components/ethernet/ethernet_component.cpp +69 -29
  157. esphome/components/ethernet/ethernet_component.h +18 -10
  158. esphome/components/event/__init__.py +5 -2
  159. esphome/components/ezo/ezo.h +0 -1
  160. esphome/components/ezo_pmp/ezo_pmp.h +0 -1
  161. esphome/components/fan/__init__.py +5 -2
  162. esphome/components/feedback/feedback_cover.h +0 -1
  163. esphome/components/font/__init__.py +92 -82
  164. esphome/components/font/font.cpp +9 -2
  165. esphome/components/font/font.h +20 -5
  166. esphome/components/fs3000/fs3000.h +0 -1
  167. esphome/components/gcja5/gcja5.h +0 -1
  168. esphome/components/gl_r01_i2c/__init__.py +0 -0
  169. esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +68 -0
  170. esphome/components/gl_r01_i2c/gl_r01_i2c.h +22 -0
  171. esphome/components/gl_r01_i2c/sensor.py +36 -0
  172. esphome/components/gp8403/gp8403.h +0 -1
  173. esphome/components/gpio/binary_sensor/__init__.py +17 -0
  174. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +77 -3
  175. esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +40 -0
  176. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h +0 -2
  177. esphome/components/he60r/he60r.h +0 -1
  178. esphome/components/heatpumpir/climate.py +2 -1
  179. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  180. esphome/components/heatpumpir/heatpumpir.h +1 -0
  181. esphome/components/honeywellabp2_i2c/honeywellabp2.h +0 -1
  182. esphome/components/host/__init__.py +3 -1
  183. esphome/components/host/helpers.cpp +57 -0
  184. esphome/components/http_request/__init__.py +19 -1
  185. esphome/components/http_request/http_request.h +1 -1
  186. esphome/components/http_request/http_request_arduino.h +1 -0
  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 +421 -268
  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 +5 -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 +132 -47
  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 +34 -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 +2 -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 +0 -4
  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/usb_host/usb_host_client.cpp +10 -10
  477. esphome/components/usb_uart/cp210x.cpp +1 -1
  478. esphome/components/usb_uart/usb_uart.cpp +41 -44
  479. esphome/components/usb_uart/usb_uart.h +4 -3
  480. esphome/components/valve/__init__.py +5 -2
  481. esphome/components/vbus/vbus.h +0 -1
  482. esphome/components/veml3235/veml3235.h +0 -1
  483. esphome/components/veml7700/veml7700.h +0 -1
  484. esphome/components/vl53l0x/vl53l0x_sensor.h +0 -1
  485. esphome/components/voice_assistant/voice_assistant.cpp +4 -4
  486. esphome/components/watchdog/watchdog.cpp +0 -4
  487. esphome/components/waveshare_epaper/waveshare_epaper.cpp +6 -6
  488. esphome/components/web_server/__init__.py +34 -19
  489. esphome/components/web_server/ota/__init__.py +32 -0
  490. esphome/components/web_server/ota/ota_web_server.cpp +210 -0
  491. esphome/components/web_server/ota/ota_web_server.h +26 -0
  492. esphome/components/web_server/web_server.cpp +305 -427
  493. esphome/components/web_server/web_server.h +33 -23
  494. esphome/components/web_server/web_server_v1.cpp +4 -5
  495. esphome/components/web_server_base/__init__.py +5 -2
  496. esphome/components/web_server_base/web_server_base.cpp +2 -94
  497. esphome/components/web_server_base/web_server_base.h +5 -25
  498. esphome/components/web_server_idf/multipart.cpp +254 -0
  499. esphome/components/web_server_idf/multipart.h +86 -0
  500. esphome/components/web_server_idf/utils.cpp +32 -0
  501. esphome/components/web_server_idf/utils.h +10 -0
  502. esphome/components/web_server_idf/web_server_idf.cpp +162 -16
  503. esphome/components/web_server_idf/web_server_idf.h +11 -10
  504. esphome/components/wiegand/wiegand.cpp +2 -2
  505. esphome/components/wifi/__init__.py +18 -0
  506. esphome/components/wifi/wifi_component.cpp +17 -22
  507. esphome/components/wifi/wifi_component.h +27 -23
  508. esphome/components/wifi/wifi_component_esp32_arduino.cpp +52 -59
  509. esphome/components/wifi/wifi_component_esp8266.cpp +46 -46
  510. esphome/components/wifi/wifi_component_esp_idf.cpp +35 -36
  511. esphome/components/wifi/wifi_component_libretiny.cpp +26 -27
  512. esphome/components/wifi/wifi_component_pico_w.cpp +3 -3
  513. esphome/components/wifi_info/wifi_info_text_sensor.cpp +6 -6
  514. esphome/components/wireguard/__init__.py +2 -11
  515. esphome/components/xiaomi_ble/xiaomi_ble.cpp +13 -1
  516. esphome/components/xiaomi_ble/xiaomi_ble.h +1 -0
  517. esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +0 -1
  518. esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +0 -1
  519. esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +0 -1
  520. esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +0 -1
  521. esphome/components/xiaomi_gcls002/xiaomi_gcls002.h +0 -1
  522. esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +0 -1
  523. esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h +0 -1
  524. esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h +0 -1
  525. esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h +0 -1
  526. esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +0 -1
  527. esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +0 -1
  528. esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +0 -1
  529. esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +0 -1
  530. esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h +0 -1
  531. esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +0 -1
  532. esphome/components/xiaomi_miscale/xiaomi_miscale.h +0 -1
  533. esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +0 -1
  534. esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h +0 -1
  535. esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +0 -1
  536. esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h +0 -1
  537. esphome/components/xiaomi_xmwsdj04mmc/__init__.py +0 -0
  538. esphome/components/xiaomi_xmwsdj04mmc/sensor.py +77 -0
  539. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +77 -0
  540. esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +36 -0
  541. esphome/components/zio_ultrasonic/zio_ultrasonic.h +0 -2
  542. esphome/components/zyaura/zyaura.h +0 -1
  543. esphome/config.py +88 -22
  544. esphome/config_helpers.py +74 -1
  545. esphome/config_validation.py +12 -1
  546. esphome/const.py +65 -10
  547. esphome/core/__init__.py +18 -2
  548. esphome/core/application.cpp +163 -10
  549. esphome/core/application.h +145 -165
  550. esphome/core/area.h +19 -0
  551. esphome/core/automation.h +58 -9
  552. esphome/core/color.cpp +3 -5
  553. esphome/core/color.h +16 -16
  554. esphome/core/component.cpp +151 -18
  555. esphome/core/component.h +98 -4
  556. esphome/core/component_iterator.cpp +11 -9
  557. esphome/core/component_iterator.h +12 -10
  558. esphome/core/config.py +155 -6
  559. esphome/core/controller.cpp +4 -2
  560. esphome/core/controller.h +1 -1
  561. esphome/core/datatypes.h +2 -2
  562. esphome/core/defines.h +17 -2
  563. esphome/core/device.h +20 -0
  564. esphome/core/entity_base.cpp +20 -15
  565. esphome/core/entity_base.h +76 -0
  566. esphome/core/entity_helpers.py +168 -1
  567. esphome/core/event_pool.h +81 -0
  568. esphome/core/helpers.cpp +75 -230
  569. esphome/core/helpers.h +164 -104
  570. esphome/core/lock_free_queue.h +151 -0
  571. esphome/core/log.cpp +2 -2
  572. esphome/core/log.h +2 -0
  573. esphome/core/optional.h +5 -0
  574. esphome/core/ring_buffer.cpp +2 -2
  575. esphome/core/scheduler.cpp +275 -103
  576. esphome/core/scheduler.h +154 -17
  577. esphome/core/time.cpp +5 -5
  578. esphome/core/time.h +5 -5
  579. esphome/cpp_generator.py +17 -0
  580. esphome/cpp_helpers.py +0 -22
  581. esphome/cpp_types.py +3 -1
  582. esphome/dashboard/entries.py +1 -1
  583. esphome/dashboard/util/text.py +5 -21
  584. esphome/dashboard/web_server.py +9 -1
  585. esphome/helpers.py +47 -0
  586. esphome/loader.py +15 -1
  587. esphome/pins.py +14 -8
  588. esphome/wizard.py +17 -4
  589. esphome/writer.py +21 -3
  590. esphome/yaml_util.py +0 -2
  591. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.dist-info}/METADATA +10 -9
  592. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.dist-info}/RECORD +597 -538
  593. esphome/components/api/api_pb2_size.h +0 -361
  594. esphome/components/esp32_ble/ble_event_pool.h +0 -72
  595. esphome/components/esp32_ble/queue.h +0 -85
  596. esphome/components/esp32_hall/esp32_hall.cpp +0 -25
  597. esphome/components/esp32_hall/esp32_hall.h +0 -23
  598. esphome/components/esp32_touch/esp32_touch.cpp +0 -355
  599. esphome/components/ld2410/button/reset_button.cpp +0 -9
  600. esphome/components/ld2450/button/reset_button.cpp +0 -9
  601. esphome/components/openthread/tlv.py +0 -65
  602. /esphome/{dashboard/enum.py → enum.py} +0 -0
  603. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.dist-info}/WHEEL +0 -0
  604. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.dist-info}/entry_points.txt +0 -0
  605. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.dist-info}/licenses/LICENSE +0 -0
  606. {esphome-2025.6.3.dist-info → esphome-2025.7.0b2.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;
@@ -1756,12 +1640,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
1756
1640
  event_types.add(event_type);
1757
1641
  }
1758
1642
  root["device_class"] = obj->get_device_class();
1759
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1760
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1761
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1762
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1763
- }
1764
- }
1643
+ this->add_sorting_info_(root, obj);
1765
1644
  }
1766
1645
  });
1767
1646
  }
@@ -1775,26 +1654,22 @@ void WebServer::on_update(update::UpdateEntity *obj) {
1775
1654
  }
1776
1655
  void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1777
1656
  for (update::UpdateEntity *obj : App.get_updates()) {
1778
- if (obj->get_object_id() != match.id)
1657
+ if (!match.id_equals(obj->get_object_id()))
1779
1658
  continue;
1780
1659
 
1781
- if (request->method() == HTTP_GET && match.method.empty()) {
1782
- auto detail = DETAIL_STATE;
1783
- auto *param = request->getParam("detail");
1784
- if (param && param->value() == "all") {
1785
- detail = DETAIL_ALL;
1786
- }
1660
+ if (request->method() == HTTP_GET && match.method_empty()) {
1661
+ auto detail = get_request_detail(request);
1787
1662
  std::string data = this->update_json(obj, detail);
1788
1663
  request->send(200, "application/json", data.c_str());
1789
1664
  return;
1790
1665
  }
1791
1666
 
1792
- if (match.method != "install") {
1667
+ if (!match.method_equals("install")) {
1793
1668
  request->send(404);
1794
1669
  return;
1795
1670
  }
1796
1671
 
1797
- this->schedule_([obj]() mutable { obj->perform(); });
1672
+ this->defer([obj]() mutable { obj->perform(); });
1798
1673
  request->send(200);
1799
1674
  return;
1800
1675
  }
@@ -1829,18 +1704,13 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
1829
1704
  root["title"] = obj->update_info.title;
1830
1705
  root["summary"] = obj->update_info.summary;
1831
1706
  root["release_url"] = obj->update_info.release_url;
1832
- if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1833
- root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1834
- if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
1835
- root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
1836
- }
1837
- }
1707
+ this->add_sorting_info_(root, obj);
1838
1708
  }
1839
1709
  });
1840
1710
  }
1841
1711
  #endif
1842
1712
 
1843
- bool WebServer::canHandle(AsyncWebServerRequest *request) {
1713
+ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
1844
1714
  if (request->url() == "/")
1845
1715
  return true;
1846
1716
 
@@ -1862,116 +1732,114 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
1862
1732
 
1863
1733
  #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1864
1734
  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1865
- #ifdef USE_ARDUINO
1866
- // Header needs to be added to interesting header list for it to not be
1867
- // nuked by the time we handle the request later.
1868
- // Only required in Arduino framework.
1869
- request->addInterestingHeader(HEADER_CORS_REQ_PNA);
1870
- #endif
1871
1735
  return true;
1872
1736
  }
1873
1737
  #endif
1874
1738
 
1875
- 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);
1876
1744
  if (!match.valid)
1877
1745
  return false;
1878
1746
  #ifdef USE_SENSOR
1879
- if (request->method() == HTTP_GET && match.domain == "sensor")
1747
+ if (request->method() == HTTP_GET && match.domain_equals("sensor"))
1880
1748
  return true;
1881
1749
  #endif
1882
1750
 
1883
1751
  #ifdef USE_SWITCH
1884
- 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"))
1885
1753
  return true;
1886
1754
  #endif
1887
1755
 
1888
1756
  #ifdef USE_BUTTON
1889
- 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"))
1890
1758
  return true;
1891
1759
  #endif
1892
1760
 
1893
1761
  #ifdef USE_BINARY_SENSOR
1894
- if (request->method() == HTTP_GET && match.domain == "binary_sensor")
1762
+ if (request->method() == HTTP_GET && match.domain_equals("binary_sensor"))
1895
1763
  return true;
1896
1764
  #endif
1897
1765
 
1898
1766
  #ifdef USE_FAN
1899
- 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"))
1900
1768
  return true;
1901
1769
  #endif
1902
1770
 
1903
1771
  #ifdef USE_LIGHT
1904
- 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"))
1905
1773
  return true;
1906
1774
  #endif
1907
1775
 
1908
1776
  #ifdef USE_TEXT_SENSOR
1909
- if (request->method() == HTTP_GET && match.domain == "text_sensor")
1777
+ if (request->method() == HTTP_GET && match.domain_equals("text_sensor"))
1910
1778
  return true;
1911
1779
  #endif
1912
1780
 
1913
1781
  #ifdef USE_COVER
1914
- 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"))
1915
1783
  return true;
1916
1784
  #endif
1917
1785
 
1918
1786
  #ifdef USE_NUMBER
1919
- 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"))
1920
1788
  return true;
1921
1789
  #endif
1922
1790
 
1923
1791
  #ifdef USE_DATETIME_DATE
1924
- 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"))
1925
1793
  return true;
1926
1794
  #endif
1927
1795
 
1928
1796
  #ifdef USE_DATETIME_TIME
1929
- 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"))
1930
1798
  return true;
1931
1799
  #endif
1932
1800
 
1933
1801
  #ifdef USE_DATETIME_DATETIME
1934
- 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"))
1935
1803
  return true;
1936
1804
  #endif
1937
1805
 
1938
1806
  #ifdef USE_TEXT
1939
- 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"))
1940
1808
  return true;
1941
1809
  #endif
1942
1810
 
1943
1811
  #ifdef USE_SELECT
1944
- 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"))
1945
1813
  return true;
1946
1814
  #endif
1947
1815
 
1948
1816
  #ifdef USE_CLIMATE
1949
- 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"))
1950
1818
  return true;
1951
1819
  #endif
1952
1820
 
1953
1821
  #ifdef USE_LOCK
1954
- 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"))
1955
1823
  return true;
1956
1824
  #endif
1957
1825
 
1958
1826
  #ifdef USE_VALVE
1959
- 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"))
1960
1828
  return true;
1961
1829
  #endif
1962
1830
 
1963
1831
  #ifdef USE_ALARM_CONTROL_PANEL
1964
- 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"))
1965
1833
  return true;
1966
1834
  #endif
1967
1835
 
1968
1836
  #ifdef USE_EVENT
1969
- if (request->method() == HTTP_GET && match.domain == "event")
1837
+ if (request->method() == HTTP_GET && match.domain_equals("event"))
1970
1838
  return true;
1971
1839
  #endif
1972
1840
 
1973
1841
  #ifdef USE_UPDATE
1974
- 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"))
1975
1843
  return true;
1976
1844
  #endif
1977
1845
 
@@ -2011,114 +1879,117 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2011
1879
  }
2012
1880
  #endif
2013
1881
 
2014
- 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
+
2015
1886
  #ifdef USE_SENSOR
2016
- if (match.domain == "sensor") {
1887
+ if (match.domain_equals("sensor")) {
2017
1888
  this->handle_sensor_request(request, match);
2018
1889
  return;
2019
1890
  }
2020
1891
  #endif
2021
1892
 
2022
1893
  #ifdef USE_SWITCH
2023
- if (match.domain == "switch") {
1894
+ if (match.domain_equals("switch")) {
2024
1895
  this->handle_switch_request(request, match);
2025
1896
  return;
2026
1897
  }
2027
1898
  #endif
2028
1899
 
2029
1900
  #ifdef USE_BUTTON
2030
- if (match.domain == "button") {
1901
+ if (match.domain_equals("button")) {
2031
1902
  this->handle_button_request(request, match);
2032
1903
  return;
2033
1904
  }
2034
1905
  #endif
2035
1906
 
2036
1907
  #ifdef USE_BINARY_SENSOR
2037
- if (match.domain == "binary_sensor") {
1908
+ if (match.domain_equals("binary_sensor")) {
2038
1909
  this->handle_binary_sensor_request(request, match);
2039
1910
  return;
2040
1911
  }
2041
1912
  #endif
2042
1913
 
2043
1914
  #ifdef USE_FAN
2044
- if (match.domain == "fan") {
1915
+ if (match.domain_equals("fan")) {
2045
1916
  this->handle_fan_request(request, match);
2046
1917
  return;
2047
1918
  }
2048
1919
  #endif
2049
1920
 
2050
1921
  #ifdef USE_LIGHT
2051
- if (match.domain == "light") {
1922
+ if (match.domain_equals("light")) {
2052
1923
  this->handle_light_request(request, match);
2053
1924
  return;
2054
1925
  }
2055
1926
  #endif
2056
1927
 
2057
1928
  #ifdef USE_TEXT_SENSOR
2058
- if (match.domain == "text_sensor") {
1929
+ if (match.domain_equals("text_sensor")) {
2059
1930
  this->handle_text_sensor_request(request, match);
2060
1931
  return;
2061
1932
  }
2062
1933
  #endif
2063
1934
 
2064
1935
  #ifdef USE_COVER
2065
- if (match.domain == "cover") {
1936
+ if (match.domain_equals("cover")) {
2066
1937
  this->handle_cover_request(request, match);
2067
1938
  return;
2068
1939
  }
2069
1940
  #endif
2070
1941
 
2071
1942
  #ifdef USE_NUMBER
2072
- if (match.domain == "number") {
1943
+ if (match.domain_equals("number")) {
2073
1944
  this->handle_number_request(request, match);
2074
1945
  return;
2075
1946
  }
2076
1947
  #endif
2077
1948
 
2078
1949
  #ifdef USE_DATETIME_DATE
2079
- if (match.domain == "date") {
1950
+ if (match.domain_equals("date")) {
2080
1951
  this->handle_date_request(request, match);
2081
1952
  return;
2082
1953
  }
2083
1954
  #endif
2084
1955
 
2085
1956
  #ifdef USE_DATETIME_TIME
2086
- if (match.domain == "time") {
1957
+ if (match.domain_equals("time")) {
2087
1958
  this->handle_time_request(request, match);
2088
1959
  return;
2089
1960
  }
2090
1961
  #endif
2091
1962
 
2092
1963
  #ifdef USE_DATETIME_DATETIME
2093
- if (match.domain == "datetime") {
1964
+ if (match.domain_equals("datetime")) {
2094
1965
  this->handle_datetime_request(request, match);
2095
1966
  return;
2096
1967
  }
2097
1968
  #endif
2098
1969
 
2099
1970
  #ifdef USE_TEXT
2100
- if (match.domain == "text") {
1971
+ if (match.domain_equals("text")) {
2101
1972
  this->handle_text_request(request, match);
2102
1973
  return;
2103
1974
  }
2104
1975
  #endif
2105
1976
 
2106
1977
  #ifdef USE_SELECT
2107
- if (match.domain == "select") {
1978
+ if (match.domain_equals("select")) {
2108
1979
  this->handle_select_request(request, match);
2109
1980
  return;
2110
1981
  }
2111
1982
  #endif
2112
1983
 
2113
1984
  #ifdef USE_CLIMATE
2114
- if (match.domain == "climate") {
1985
+ if (match.domain_equals("climate")) {
2115
1986
  this->handle_climate_request(request, match);
2116
1987
  return;
2117
1988
  }
2118
1989
  #endif
2119
1990
 
2120
1991
  #ifdef USE_LOCK
2121
- if (match.domain == "lock") {
1992
+ if (match.domain_equals("lock")) {
2122
1993
  this->handle_lock_request(request, match);
2123
1994
 
2124
1995
  return;
@@ -2126,14 +1997,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2126
1997
  #endif
2127
1998
 
2128
1999
  #ifdef USE_VALVE
2129
- if (match.domain == "valve") {
2000
+ if (match.domain_equals("valve")) {
2130
2001
  this->handle_valve_request(request, match);
2131
2002
  return;
2132
2003
  }
2133
2004
  #endif
2134
2005
 
2135
2006
  #ifdef USE_ALARM_CONTROL_PANEL
2136
- if (match.domain == "alarm_control_panel") {
2007
+ if (match.domain_equals("alarm_control_panel")) {
2137
2008
  this->handle_alarm_control_panel_request(request, match);
2138
2009
 
2139
2010
  return;
@@ -2141,15 +2012,31 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
2141
2012
  #endif
2142
2013
 
2143
2014
  #ifdef USE_UPDATE
2144
- if (match.domain == "update") {
2015
+ if (match.domain_equals("update")) {
2145
2016
  this->handle_update_request(request, match);
2146
2017
  return;
2147
2018
  }
2148
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");
2149
2024
  }
2150
2025
 
2151
- bool WebServer::isRequestHandlerTrivial() { return false; }
2026
+ bool WebServer::isRequestHandlerTrivial() const { return false; }
2152
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
2153
2040
  void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) {
2154
2041
  this->sorting_entitys_[entity] = SortingComponents{weight, group};
2155
2042
  }
@@ -2157,16 +2044,7 @@ void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t gro
2157
2044
  void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) {
2158
2045
  this->sorting_groups_[group_id] = SortingGroup{group_name, weight};
2159
2046
  }
2160
-
2161
- void WebServer::schedule_(std::function<void()> &&f) {
2162
- #ifdef USE_ESP32
2163
- xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY);
2164
- to_schedule_.push_back(std::move(f));
2165
- xSemaphoreGive(this->to_schedule_lock_);
2166
- #else
2167
- this->defer(std::move(f));
2168
2047
  #endif
2169
- }
2170
2048
 
2171
2049
  } // namespace web_server
2172
2050
  } // namespace esphome