esphome 2025.4.2__py3-none-any.whl → 2025.5.0b3__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 (440) hide show
  1. esphome/__main__.py +16 -14
  2. esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
  3. esphome/components/adc/__init__.py +51 -34
  4. esphome/components/airthings_wave_base/__init__.py +1 -1
  5. esphome/components/alarm_control_panel/__init__.py +37 -2
  6. esphome/components/am43/cover/__init__.py +4 -5
  7. esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
  8. esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
  9. esphome/components/analog_threshold/binary_sensor.py +10 -8
  10. esphome/components/anova/climate.py +4 -5
  11. esphome/components/api/__init__.py +25 -8
  12. esphome/components/api/api_connection.cpp +81 -13
  13. esphome/components/api/api_connection.h +13 -1
  14. esphome/components/api/api_frame_helper.cpp +232 -177
  15. esphome/components/api/api_frame_helper.h +61 -8
  16. esphome/components/api/api_noise_context.h +13 -4
  17. esphome/components/api/api_pb2.cpp +1422 -1
  18. esphome/components/api/api_pb2.h +255 -1
  19. esphome/components/api/api_pb2_service.cpp +162 -49
  20. esphome/components/api/api_pb2_service.h +90 -51
  21. esphome/components/api/api_pb2_size.h +361 -0
  22. esphome/components/api/api_server.cpp +110 -34
  23. esphome/components/api/api_server.h +8 -0
  24. esphome/components/api/proto.h +86 -17
  25. esphome/components/as7341/as7341.h +1 -1
  26. esphome/components/atm90e32/__init__.py +1 -0
  27. esphome/components/atm90e32/atm90e32.cpp +576 -199
  28. esphome/components/atm90e32/atm90e32.h +128 -31
  29. esphome/components/atm90e32/atm90e32_reg.h +4 -2
  30. esphome/components/atm90e32/button/__init__.py +62 -10
  31. esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
  32. esphome/components/atm90e32/button/atm90e32_button.h +36 -4
  33. esphome/components/atm90e32/number/__init__.py +130 -0
  34. esphome/components/atm90e32/number/atm90e32_number.h +16 -0
  35. esphome/components/atm90e32/sensor.py +21 -4
  36. esphome/components/atm90e32/text_sensor/__init__.py +48 -0
  37. esphome/components/audio/__init__.py +96 -49
  38. esphome/components/audio/audio.h +48 -0
  39. esphome/components/audio/audio_decoder.cpp +1 -1
  40. esphome/components/audio/audio_resampler.cpp +2 -0
  41. esphome/components/audio/audio_resampler.h +1 -0
  42. esphome/components/ballu/climate.py +2 -9
  43. esphome/components/bang_bang/climate.py +5 -6
  44. esphome/components/bedjet/bedjet_hub.cpp +1 -0
  45. esphome/components/bedjet/climate/__init__.py +3 -8
  46. esphome/components/bedjet/fan/__init__.py +2 -11
  47. esphome/components/binary/fan/__init__.py +13 -16
  48. esphome/components/binary_sensor/__init__.py +13 -10
  49. esphome/components/bl0906/constants.h +16 -16
  50. esphome/components/ble_client/text_sensor/__init__.py +3 -5
  51. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
  52. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
  53. esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
  54. esphome/components/button/__init__.py +11 -8
  55. esphome/components/canbus/canbus.cpp +3 -0
  56. esphome/components/canbus/canbus.h +16 -0
  57. esphome/components/ccs811/sensor.py +9 -6
  58. esphome/components/climate/__init__.py +35 -2
  59. esphome/components/climate/climate_mode.h +1 -1
  60. esphome/components/climate/climate_traits.h +63 -57
  61. esphome/components/climate_ir/__init__.py +57 -17
  62. esphome/components/climate_ir_lg/climate.py +2 -5
  63. esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
  64. esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
  65. esphome/components/color/__init__.py +2 -0
  66. esphome/components/const/__init__.py +5 -0
  67. esphome/components/coolix/climate.py +2 -9
  68. esphome/components/copy/cover/__init__.py +10 -9
  69. esphome/components/copy/fan/__init__.py +11 -9
  70. esphome/components/copy/lock/__init__.py +11 -9
  71. esphome/components/copy/text/__init__.py +9 -6
  72. esphome/components/cover/__init__.py +37 -2
  73. esphome/components/cse7766/cse7766.cpp +2 -1
  74. esphome/components/cst226/binary_sensor/__init__.py +28 -0
  75. esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
  76. esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
  77. esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
  78. esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
  79. esphome/components/current_based/cover.py +37 -36
  80. esphome/components/current_based/current_based_cover.cpp +2 -1
  81. esphome/components/daikin/climate.py +2 -9
  82. esphome/components/daikin/daikin.cpp +15 -9
  83. esphome/components/daikin/daikin.h +5 -5
  84. esphome/components/daikin_arc/climate.py +2 -7
  85. esphome/components/daikin_brc/climate.py +3 -5
  86. esphome/components/dallas_temp/dallas_temp.cpp +17 -24
  87. esphome/components/dallas_temp/dallas_temp.h +0 -1
  88. esphome/components/daly_bms/daly_bms.cpp +2 -1
  89. esphome/components/debug/debug_component.cpp +6 -1
  90. esphome/components/debug/debug_component.h +6 -0
  91. esphome/components/debug/debug_esp32.cpp +109 -254
  92. esphome/components/debug/sensor.py +14 -0
  93. esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
  94. esphome/components/delonghi/climate.py +2 -9
  95. esphome/components/demo/__init__.py +18 -20
  96. esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
  97. esphome/components/dps310/sensor.py +6 -6
  98. esphome/components/ee895/sensor.py +9 -9
  99. esphome/components/emmeti/climate.py +2 -9
  100. esphome/components/endstop/cover.py +17 -16
  101. esphome/components/endstop/endstop_cover.cpp +2 -1
  102. esphome/components/ens160_base/__init__.py +12 -9
  103. esphome/components/esp32/__init__.py +60 -3
  104. esphome/components/esp32/core.cpp +11 -5
  105. esphome/components/esp32/gpio.cpp +86 -24
  106. esphome/components/esp32/gpio.py +15 -16
  107. esphome/components/esp32/gpio_esp32.py +1 -2
  108. esphome/components/esp32/gpio_esp32_c2.py +1 -1
  109. esphome/components/esp32/gpio_esp32_c3.py +1 -1
  110. esphome/components/esp32/gpio_esp32_c6.py +1 -1
  111. esphome/components/esp32/gpio_esp32_h2.py +1 -1
  112. esphome/components/esp32_ble/ble.cpp +1 -0
  113. esphome/components/esp32_ble/ble.h +5 -3
  114. esphome/components/esp32_ble/ble_advertising.cpp +2 -1
  115. esphome/components/esp32_ble/ble_advertising.h +1 -0
  116. esphome/components/esp32_ble_server/__init__.py +3 -0
  117. esphome/components/esp32_ble_tracker/__init__.py +7 -1
  118. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
  119. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
  120. esphome/components/esp32_camera/esp32_camera.cpp +2 -1
  121. esphome/components/esp32_camera/esp32_camera.h +1 -1
  122. esphome/components/esp32_can/esp32_can.cpp +1 -1
  123. esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
  124. esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
  125. esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
  126. esphome/components/esp32_rmt_led_strip/light.py +9 -1
  127. esphome/components/esp32_touch/esp32_touch.cpp +1 -1
  128. esphome/components/esp8266/gpio.cpp +69 -8
  129. esphome/components/ethernet/ethernet_component.cpp +1 -1
  130. esphome/components/event/__init__.py +13 -10
  131. esphome/components/factory_reset/switch/__init__.py +7 -21
  132. esphome/components/fan/__init__.py +52 -5
  133. esphome/components/fastled_base/__init__.py +1 -4
  134. esphome/components/fastled_base/fastled_light.cpp +1 -1
  135. esphome/components/feedback/cover.py +38 -33
  136. esphome/components/feedback/feedback_cover.cpp +2 -1
  137. esphome/components/fujitsu_general/climate.py +2 -9
  138. esphome/components/gcja5/gcja5.cpp +2 -1
  139. esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
  140. esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
  141. esphome/components/gpio_expander/cached_gpio.h +22 -7
  142. esphome/components/gps/__init__.py +47 -17
  143. esphome/components/gps/gps.cpp +42 -23
  144. esphome/components/gps/gps.h +17 -13
  145. esphome/components/graph/__init__.py +1 -2
  146. esphome/components/gree/climate.py +4 -6
  147. esphome/components/gree/gree.cpp +16 -2
  148. esphome/components/gree/gree.h +2 -2
  149. esphome/components/growatt_solar/growatt_solar.cpp +2 -1
  150. esphome/components/haier/climate.py +37 -34
  151. esphome/components/hbridge/fan/__init__.py +19 -17
  152. esphome/components/he60r/cover.py +4 -5
  153. esphome/components/heatpumpir/climate.py +3 -6
  154. esphome/components/hitachi_ac344/climate.py +2 -9
  155. esphome/components/hitachi_ac424/climate.py +2 -9
  156. esphome/components/hm3301/hm3301.h +1 -1
  157. esphome/components/hte501/sensor.py +6 -6
  158. esphome/components/http_request/__init__.py +39 -6
  159. esphome/components/http_request/http_request.cpp +20 -0
  160. esphome/components/http_request/http_request.h +57 -15
  161. esphome/components/http_request/http_request_arduino.cpp +22 -6
  162. esphome/components/http_request/http_request_arduino.h +4 -3
  163. esphome/components/http_request/http_request_host.cpp +141 -0
  164. esphome/components/http_request/http_request_host.h +37 -0
  165. esphome/components/http_request/http_request_idf.cpp +35 -3
  166. esphome/components/http_request/http_request_idf.h +10 -3
  167. esphome/components/http_request/httplib.h +9691 -0
  168. esphome/components/http_request/update/__init__.py +11 -8
  169. esphome/components/hyt271/sensor.py +6 -6
  170. esphome/components/i2c/i2c.h +4 -0
  171. esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
  172. esphome/components/i2s_audio/__init__.py +131 -22
  173. esphome/components/i2s_audio/i2s_audio.h +44 -4
  174. esphome/components/i2s_audio/media_player/__init__.py +19 -9
  175. esphome/components/i2s_audio/microphone/__init__.py +63 -5
  176. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
  177. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
  178. esphome/components/i2s_audio/speaker/__init__.py +31 -5
  179. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
  180. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
  181. esphome/components/ili9xxx/ili9xxx_init.h +1 -1
  182. esphome/components/image/__init__.py +37 -17
  183. esphome/components/image/image.cpp +25 -8
  184. esphome/components/internal_temperature/internal_temperature.cpp +6 -4
  185. esphome/components/key_collector/__init__.py +35 -0
  186. esphome/components/key_collector/key_collector.cpp +8 -0
  187. esphome/components/key_collector/key_collector.h +10 -0
  188. esphome/components/kuntze/kuntze.cpp +2 -1
  189. esphome/components/ld2410/ld2410.h +1 -1
  190. esphome/components/ld2450/ld2450.h +1 -1
  191. esphome/components/light/__init__.py +57 -0
  192. esphome/components/lock/__init__.py +51 -4
  193. esphome/components/lock/automation.h +2 -13
  194. esphome/components/logger/__init__.py +22 -0
  195. esphome/components/logger/logger.cpp +154 -103
  196. esphome/components/logger/logger.h +211 -36
  197. esphome/components/logger/task_log_buffer.cpp +138 -0
  198. esphome/components/logger/task_log_buffer.h +69 -0
  199. esphome/components/lvgl/__init__.py +13 -5
  200. esphome/components/lvgl/automation.py +50 -1
  201. esphome/components/lvgl/defines.py +0 -1
  202. esphome/components/lvgl/lvgl_esphome.cpp +5 -1
  203. esphome/components/lvgl/text/__init__.py +1 -2
  204. esphome/components/mapping/__init__.py +134 -0
  205. esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
  206. esphome/components/max7219digit/max7219digit.cpp +28 -27
  207. esphome/components/mdns/__init__.py +11 -5
  208. esphome/components/mdns/mdns_component.cpp +11 -5
  209. esphome/components/mdns/mdns_component.h +3 -2
  210. esphome/components/mdns/mdns_esp32.cpp +4 -3
  211. esphome/components/mdns/mdns_esp8266.cpp +4 -2
  212. esphome/components/mdns/mdns_libretiny.cpp +4 -2
  213. esphome/components/mdns/mdns_rp2040.cpp +4 -2
  214. esphome/components/media_player/__init__.py +33 -1
  215. esphome/components/mhz19/sensor.py +11 -7
  216. esphome/components/micro_wake_word/__init__.py +99 -31
  217. esphome/components/micro_wake_word/automation.h +54 -0
  218. esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
  219. esphome/components/micro_wake_word/micro_wake_word.h +58 -105
  220. esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
  221. esphome/components/micro_wake_word/streaming_model.cpp +158 -41
  222. esphome/components/micro_wake_word/streaming_model.h +85 -13
  223. esphome/components/microphone/__init__.py +139 -9
  224. esphome/components/microphone/automation.h +14 -2
  225. esphome/components/microphone/microphone.cpp +21 -0
  226. esphome/components/microphone/microphone.h +14 -5
  227. esphome/components/microphone/microphone_source.cpp +95 -0
  228. esphome/components/microphone/microphone_source.h +80 -0
  229. esphome/components/mics_4514/sensor.py +25 -14
  230. esphome/components/midea/climate.py +3 -4
  231. esphome/components/midea_ir/climate.py +3 -5
  232. esphome/components/mipi_spi/__init__.py +15 -0
  233. esphome/components/mipi_spi/display.py +474 -0
  234. esphome/components/mipi_spi/mipi_spi.cpp +481 -0
  235. esphome/components/mipi_spi/mipi_spi.h +171 -0
  236. esphome/components/mipi_spi/models/__init__.py +65 -0
  237. esphome/components/mipi_spi/models/amoled.py +72 -0
  238. esphome/components/mipi_spi/models/commands.py +82 -0
  239. esphome/components/mipi_spi/models/cyd.py +10 -0
  240. esphome/components/mipi_spi/models/ili.py +749 -0
  241. esphome/components/mipi_spi/models/jc.py +260 -0
  242. esphome/components/mipi_spi/models/lanbon.py +15 -0
  243. esphome/components/mipi_spi/models/lilygo.py +60 -0
  244. esphome/components/mipi_spi/models/waveshare.py +139 -0
  245. esphome/components/mitsubishi/climate.py +2 -5
  246. esphome/components/mitsubishi/mitsubishi.cpp +9 -9
  247. esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
  248. esphome/components/mixer/speaker/mixer_speaker.h +1 -3
  249. esphome/components/mlx90393/sensor.py +5 -0
  250. esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
  251. esphome/components/mlx90393/sensor_mlx90393.h +21 -4
  252. esphome/components/modbus/modbus.cpp +2 -1
  253. esphome/components/mqtt/__init__.py +1 -1
  254. esphome/components/mqtt/mqtt_client.cpp +6 -2
  255. esphome/components/mqtt/mqtt_const.h +4 -0
  256. esphome/components/mqtt/mqtt_fan.cpp +39 -0
  257. esphome/components/mqtt/mqtt_fan.h +2 -0
  258. esphome/components/ms5611/sensor.py +6 -6
  259. esphome/components/ms8607/sensor.py +3 -3
  260. esphome/components/network/__init__.py +1 -1
  261. esphome/components/nextion/base_component.py +17 -16
  262. esphome/components/nextion/display.py +11 -2
  263. esphome/components/nextion/nextion.cpp +39 -1
  264. esphome/components/nextion/nextion.h +50 -0
  265. esphome/components/noblex/climate.py +2 -9
  266. esphome/components/number/__init__.py +12 -9
  267. esphome/components/one_wire/one_wire_bus.cpp +14 -10
  268. esphome/components/one_wire/one_wire_bus.h +14 -8
  269. esphome/components/online_image/bmp_image.cpp +48 -11
  270. esphome/components/online_image/bmp_image.h +2 -0
  271. esphome/components/opentherm/binary_sensor/__init__.py +2 -4
  272. esphome/components/opentherm/number/__init__.py +11 -20
  273. esphome/components/opentherm/sensor/__init__.py +3 -3
  274. esphome/components/opentherm/switch/__init__.py +3 -5
  275. esphome/components/output/lock/__init__.py +11 -9
  276. esphome/components/packages/__init__.py +33 -31
  277. esphome/components/packet_transport/__init__.py +201 -0
  278. esphome/components/packet_transport/binary_sensor.py +19 -0
  279. esphome/components/packet_transport/packet_transport.cpp +534 -0
  280. esphome/components/packet_transport/packet_transport.h +154 -0
  281. esphome/components/packet_transport/sensor.py +19 -0
  282. esphome/components/pca9685/pca9685_output.cpp +2 -1
  283. esphome/components/pid/climate.py +2 -4
  284. esphome/components/pm2005/__init__.py +1 -0
  285. esphome/components/pm2005/pm2005.cpp +123 -0
  286. esphome/components/pm2005/pm2005.h +46 -0
  287. esphome/components/pm2005/sensor.py +86 -0
  288. esphome/components/pmsa003i/pmsa003i.cpp +43 -16
  289. esphome/components/pmsa003i/pmsa003i.h +25 -25
  290. esphome/components/pmsx003/pmsx003.cpp +195 -230
  291. esphome/components/pmsx003/pmsx003.h +51 -33
  292. esphome/components/pmsx003/sensor.py +21 -11
  293. esphome/components/pn7150/pn7150.h +2 -2
  294. esphome/components/pn7160/pn7160.h +2 -2
  295. esphome/components/prometheus/prometheus_handler.cpp +174 -0
  296. esphome/components/prometheus/prometheus_handler.h +17 -0
  297. esphome/components/psram/__init__.py +7 -5
  298. esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
  299. esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
  300. esphome/components/pzem004t/pzem004t.cpp +2 -1
  301. esphome/components/qspi_dbi/__init__.py +0 -1
  302. esphome/components/qspi_dbi/display.py +2 -1
  303. esphome/components/qspi_dbi/models.py +1 -2
  304. esphome/components/remote_base/__init__.py +91 -0
  305. esphome/components/remote_base/beo4_protocol.cpp +153 -0
  306. esphome/components/remote_base/beo4_protocol.h +43 -0
  307. esphome/components/remote_base/gobox_protocol.cpp +131 -0
  308. esphome/components/remote_base/gobox_protocol.h +54 -0
  309. esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
  310. esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
  311. esphome/components/resampler/speaker/resampler_speaker.h +1 -1
  312. esphome/components/rf_bridge/rf_bridge.cpp +2 -1
  313. esphome/components/scd30/sensor.py +2 -3
  314. esphome/components/scd4x/sensor.py +4 -5
  315. esphome/components/sdp3x/sensor.py +2 -1
  316. esphome/components/sds011/sds011.cpp +2 -1
  317. esphome/components/select/__init__.py +19 -20
  318. esphome/components/sen5x/sen5x.cpp +55 -36
  319. esphome/components/sen5x/sensor.py +1 -1
  320. esphome/components/senseair/sensor.py +3 -3
  321. esphome/components/sensor/__init__.py +158 -14
  322. esphome/components/sensor/filter.cpp +23 -0
  323. esphome/components/sensor/filter.h +22 -0
  324. esphome/components/sgp30/sensor.py +14 -16
  325. esphome/components/sgp4x/sensor.py +1 -1
  326. esphome/components/sht4x/sht4x.cpp +43 -22
  327. esphome/components/sht4x/sht4x.h +1 -1
  328. esphome/components/shtcx/sensor.py +6 -6
  329. esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
  330. esphome/components/sml/text_sensor/__init__.py +4 -6
  331. esphome/components/sound_level/__init__.py +0 -0
  332. esphome/components/sound_level/sensor.py +97 -0
  333. esphome/components/sound_level/sound_level.cpp +194 -0
  334. esphome/components/sound_level/sound_level.h +73 -0
  335. esphome/components/speaker/media_player/__init__.py +4 -8
  336. esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
  337. esphome/components/speaker/media_player/speaker_media_player.h +0 -11
  338. esphome/components/speaker/speaker.h +4 -7
  339. esphome/components/speed/fan/__init__.py +17 -16
  340. esphome/components/spi/spi.h +11 -1
  341. esphome/components/sprinkler/__init__.py +18 -19
  342. esphome/components/sprinkler/sprinkler.cpp +6 -5
  343. esphome/components/switch/__init__.py +32 -42
  344. esphome/components/syslog/__init__.py +41 -0
  345. esphome/components/syslog/esphome_syslog.cpp +49 -0
  346. esphome/components/syslog/esphome_syslog.h +27 -0
  347. esphome/components/t6615/sensor.py +3 -3
  348. esphome/components/t6615/t6615.cpp +2 -1
  349. esphome/components/tca9555/tca9555.cpp +11 -6
  350. esphome/components/tcl112/climate.py +2 -9
  351. esphome/components/template/alarm_control_panel/__init__.py +7 -6
  352. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
  353. esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
  354. esphome/components/template/cover/__init__.py +27 -21
  355. esphome/components/template/fan/__init__.py +14 -12
  356. esphome/components/template/lock/__init__.py +20 -25
  357. esphome/components/template/lock/automation.h +18 -0
  358. esphome/components/template/text/__init__.py +4 -3
  359. esphome/components/template/valve/__init__.py +32 -21
  360. esphome/components/template/valve/automation.h +24 -0
  361. esphome/components/text/__init__.py +32 -1
  362. esphome/components/text_sensor/__init__.py +24 -29
  363. esphome/components/thermostat/climate.py +5 -5
  364. esphome/components/time_based/cover.py +17 -16
  365. esphome/components/time_based/time_based_cover.cpp +2 -1
  366. esphome/components/tm1638/switch/__init__.py +10 -7
  367. esphome/components/tormatic/cover.py +4 -5
  368. esphome/components/toshiba/climate.py +3 -5
  369. esphome/components/touchscreen/touchscreen.cpp +3 -1
  370. esphome/components/tuya/climate/__init__.py +5 -6
  371. esphome/components/tuya/cover/__init__.py +6 -11
  372. esphome/components/tuya/select/__init__.py +15 -5
  373. esphome/components/tuya/select/tuya_select.cpp +6 -1
  374. esphome/components/tuya/select/tuya_select.h +5 -1
  375. esphome/components/uart/packet_transport/__init__.py +20 -0
  376. esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
  377. esphome/components/uart/packet_transport/uart_transport.h +41 -0
  378. esphome/components/uart/switch/uart_switch.cpp +2 -1
  379. esphome/components/udp/__init__.py +126 -128
  380. esphome/components/udp/automation.h +40 -0
  381. esphome/components/udp/binary_sensor.py +3 -25
  382. esphome/components/udp/packet_transport/__init__.py +29 -0
  383. esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
  384. esphome/components/udp/packet_transport/udp_transport.h +28 -0
  385. esphome/components/udp/sensor.py +3 -25
  386. esphome/components/udp/udp_component.cpp +26 -470
  387. esphome/components/udp/udp_component.h +21 -128
  388. esphome/components/update/__init__.py +31 -1
  389. esphome/components/uponor_smatrix/climate/__init__.py +4 -9
  390. esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
  391. esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
  392. esphome/components/uptime/text_sensor/__init__.py +47 -7
  393. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
  394. esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
  395. esphome/components/valve/__init__.py +34 -3
  396. esphome/components/valve/automation.h +1 -19
  397. esphome/components/vl53l0x/sensor.py +11 -0
  398. esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
  399. esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
  400. esphome/components/voice_assistant/__init__.py +36 -10
  401. esphome/components/voice_assistant/voice_assistant.cpp +170 -144
  402. esphome/components/voice_assistant/voice_assistant.h +26 -25
  403. esphome/components/waveshare_epaper/display.py +6 -0
  404. esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
  405. esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
  406. esphome/components/whirlpool/climate.py +3 -5
  407. esphome/components/whynter/climate.py +3 -5
  408. esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
  409. esphome/components/yashima/climate.py +6 -6
  410. esphome/components/zhlt01/climate.py +2 -7
  411. esphome/config.py +13 -13
  412. esphome/config_validation.py +38 -58
  413. esphome/const.py +15 -1
  414. esphome/core/__init__.py +2 -0
  415. esphome/core/application.cpp +23 -10
  416. esphome/core/application.h +9 -1
  417. esphome/core/automation.h +4 -3
  418. esphome/core/component.cpp +28 -7
  419. esphome/core/component.h +10 -1
  420. esphome/core/defines.h +23 -17
  421. esphome/core/macros.h +4 -0
  422. esphome/core/scheduler.cpp +7 -1
  423. esphome/cpp_generator.py +6 -2
  424. esphome/dashboard/web_server.py +3 -3
  425. esphome/helpers.py +39 -0
  426. esphome/loader.py +4 -0
  427. esphome/log.py +15 -19
  428. esphome/mqtt.py +23 -10
  429. esphome/platformio_api.py +1 -1
  430. esphome/schema_extractors.py +0 -1
  431. esphome/voluptuous_schema.py +3 -1
  432. esphome/vscode.py +15 -0
  433. esphome/wizard.py +47 -37
  434. esphome/zeroconf.py +7 -3
  435. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +10 -11
  436. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +440 -380
  437. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +1 -1
  438. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
  439. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
  440. {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,8 @@ from esphome.const import (
9
9
  CONF_ID,
10
10
  CONF_METHANE,
11
11
  CONF_NITROGEN_DIOXIDE,
12
+ DEVICE_CLASS_CARBON_MONOXIDE,
13
+ DEVICE_CLASS_EMPTY,
12
14
  STATE_CLASS_MEASUREMENT,
13
15
  UNIT_PARTS_PER_MILLION,
14
16
  )
@@ -22,24 +24,33 @@ MICS4514Component = mics_4514_ns.class_(
22
24
  "MICS4514Component", cg.PollingComponent, i2c.I2CDevice
23
25
  )
24
26
 
25
- SENSORS = [
26
- CONF_CARBON_MONOXIDE,
27
- CONF_METHANE,
28
- CONF_ETHANOL,
29
- CONF_HYDROGEN,
30
- CONF_AMMONIA,
31
- CONF_NITROGEN_DIOXIDE,
32
- ]
27
+ SENSORS = {
28
+ CONF_CARBON_MONOXIDE: DEVICE_CLASS_CARBON_MONOXIDE,
29
+ CONF_METHANE: DEVICE_CLASS_EMPTY,
30
+ CONF_ETHANOL: DEVICE_CLASS_EMPTY,
31
+ CONF_HYDROGEN: DEVICE_CLASS_EMPTY,
32
+ CONF_AMMONIA: DEVICE_CLASS_EMPTY,
33
+ CONF_NITROGEN_DIOXIDE: DEVICE_CLASS_EMPTY,
34
+ }
35
+
36
+
37
+ def common_sensor_schema(*, device_class: str) -> cv.Schema:
38
+ return sensor.sensor_schema(
39
+ accuracy_decimals=2,
40
+ device_class=device_class,
41
+ state_class=STATE_CLASS_MEASUREMENT,
42
+ unit_of_measurement=UNIT_PARTS_PER_MILLION,
43
+ )
33
44
 
34
- common_sensor_schema = sensor.sensor_schema(
35
- unit_of_measurement=UNIT_PARTS_PER_MILLION,
36
- state_class=STATE_CLASS_MEASUREMENT,
37
- accuracy_decimals=2,
38
- )
39
45
 
40
46
  CONFIG_SCHEMA = (
41
47
  cv.Schema({cv.GenerateID(): cv.declare_id(MICS4514Component)})
42
- .extend({cv.Optional(sensor_type): common_sensor_schema for sensor_type in SENSORS})
48
+ .extend(
49
+ {
50
+ cv.Optional(sensor_type): common_sensor_schema(device_class=device_class)
51
+ for sensor_type, device_class in SENSORS.items()
52
+ }
53
+ )
43
54
  .extend(i2c.i2c_device_schema(0x75))
44
55
  .extend(cv.polling_component_schema("60s"))
45
56
  )
@@ -104,9 +104,9 @@ validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
104
104
  validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
105
105
 
106
106
  CONFIG_SCHEMA = cv.All(
107
- climate.CLIMATE_SCHEMA.extend(
107
+ climate.climate_schema(AirConditioner)
108
+ .extend(
108
109
  {
109
- cv.GenerateID(): cv.declare_id(AirConditioner),
110
110
  cv.Optional(CONF_PERIOD, default="1s"): cv.time_period,
111
111
  cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period,
112
112
  cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5),
@@ -259,10 +259,9 @@ async def power_inv_to_code(var, config, args):
259
259
 
260
260
 
261
261
  async def to_code(config):
262
- var = cg.new_Pvariable(config[CONF_ID])
262
+ var = await climate.new_climate(config)
263
263
  await cg.register_component(var, config)
264
264
  await uart.register_uart_device(var, config)
265
- await climate.register_climate(var, config)
266
265
  cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
267
266
  cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds))
268
267
  cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS]))
@@ -1,7 +1,7 @@
1
1
  import esphome.codegen as cg
2
2
  from esphome.components import climate_ir
3
3
  import esphome.config_validation as cv
4
- from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT
4
+ from esphome.const import CONF_USE_FAHRENHEIT
5
5
 
6
6
  AUTO_LOAD = ["climate_ir", "coolix"]
7
7
  CODEOWNERS = ["@dudanov"]
@@ -10,15 +10,13 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
10
10
  MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
11
11
 
12
12
 
13
- CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
13
+ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
14
14
  {
15
- cv.GenerateID(): cv.declare_id(MideaIR),
16
15
  cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
17
16
  }
18
17
  )
19
18
 
20
19
 
21
20
  async def to_code(config):
22
- var = cg.new_Pvariable(config[CONF_ID])
23
- await climate_ir.register_climate_ir(var, config)
21
+ var = await climate_ir.new_climate_ir(config)
24
22
  cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
@@ -0,0 +1,15 @@
1
+ CODEOWNERS = ["@clydebarrow"]
2
+
3
+ DOMAIN = "mipi_spi"
4
+
5
+ CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
6
+ CONF_SPI_16 = "spi_16"
7
+ CONF_PIXEL_MODE = "pixel_mode"
8
+ CONF_COLOR_DEPTH = "color_depth"
9
+ CONF_BUS_MODE = "bus_mode"
10
+ CONF_USE_AXIS_FLIPS = "use_axis_flips"
11
+ CONF_NATIVE_WIDTH = "native_width"
12
+ CONF_NATIVE_HEIGHT = "native_height"
13
+
14
+ MODE_RGB = "RGB"
15
+ MODE_BGR = "BGR"
@@ -0,0 +1,474 @@
1
+ import logging
2
+
3
+ from esphome import pins
4
+ import esphome.codegen as cg
5
+ from esphome.components import display, spi
6
+ from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
7
+ import esphome.config_validation as cv
8
+ from esphome.config_validation import ALLOW_EXTRA
9
+ from esphome.const import (
10
+ CONF_BRIGHTNESS,
11
+ CONF_COLOR_ORDER,
12
+ CONF_CS_PIN,
13
+ CONF_DATA_RATE,
14
+ CONF_DC_PIN,
15
+ CONF_DIMENSIONS,
16
+ CONF_ENABLE_PIN,
17
+ CONF_HEIGHT,
18
+ CONF_ID,
19
+ CONF_INIT_SEQUENCE,
20
+ CONF_INVERT_COLORS,
21
+ CONF_LAMBDA,
22
+ CONF_MIRROR_X,
23
+ CONF_MIRROR_Y,
24
+ CONF_MODEL,
25
+ CONF_OFFSET_HEIGHT,
26
+ CONF_OFFSET_WIDTH,
27
+ CONF_RESET_PIN,
28
+ CONF_ROTATION,
29
+ CONF_SWAP_XY,
30
+ CONF_TRANSFORM,
31
+ CONF_WIDTH,
32
+ )
33
+ from esphome.core import TimePeriod
34
+
35
+ from ..const import CONF_DRAW_ROUNDING
36
+ from ..lvgl.defines import CONF_COLOR_DEPTH
37
+ from . import (
38
+ CONF_BUS_MODE,
39
+ CONF_DRAW_FROM_ORIGIN,
40
+ CONF_NATIVE_HEIGHT,
41
+ CONF_NATIVE_WIDTH,
42
+ CONF_PIXEL_MODE,
43
+ CONF_SPI_16,
44
+ CONF_USE_AXIS_FLIPS,
45
+ DOMAIN,
46
+ MODE_BGR,
47
+ MODE_RGB,
48
+ )
49
+ from .models import (
50
+ DELAY_FLAG,
51
+ MADCTL_BGR,
52
+ MADCTL_MV,
53
+ MADCTL_MX,
54
+ MADCTL_MY,
55
+ MADCTL_XFLIP,
56
+ MADCTL_YFLIP,
57
+ DriverChip,
58
+ amoled,
59
+ cyd,
60
+ ili,
61
+ jc,
62
+ lanbon,
63
+ lilygo,
64
+ waveshare,
65
+ )
66
+ from .models.commands import BRIGHTNESS, DISPON, INVOFF, INVON, MADCTL, PIXFMT, SLPOUT
67
+
68
+ DEPENDENCIES = ["spi"]
69
+
70
+ LOGGER = logging.getLogger(DOMAIN)
71
+ mipi_spi_ns = cg.esphome_ns.namespace("mipi_spi")
72
+ MipiSpi = mipi_spi_ns.class_(
73
+ "MipiSpi", display.Display, display.DisplayBuffer, cg.Component, spi.SPIDevice
74
+ )
75
+ ColorOrder = display.display_ns.enum("ColorMode")
76
+ ColorBitness = display.display_ns.enum("ColorBitness")
77
+ Model = mipi_spi_ns.enum("Model")
78
+
79
+ COLOR_ORDERS = {
80
+ MODE_RGB: ColorOrder.COLOR_ORDER_RGB,
81
+ MODE_BGR: ColorOrder.COLOR_ORDER_BGR,
82
+ }
83
+
84
+ COLOR_DEPTHS = {
85
+ 8: ColorBitness.COLOR_BITNESS_332,
86
+ 16: ColorBitness.COLOR_BITNESS_565,
87
+ }
88
+ DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
89
+
90
+
91
+ DriverChip("CUSTOM", initsequence={})
92
+
93
+ MODELS = DriverChip.models
94
+ # These statements are noops, but serve to suppress linting of side-effect-only imports
95
+ for _ in (ili, jc, amoled, lilygo, lanbon, cyd, waveshare):
96
+ pass
97
+
98
+ PixelMode = mipi_spi_ns.enum("PixelMode")
99
+
100
+ PIXEL_MODE_18BIT = "18bit"
101
+ PIXEL_MODE_16BIT = "16bit"
102
+
103
+ PIXEL_MODES = {
104
+ PIXEL_MODE_16BIT: 0x55,
105
+ PIXEL_MODE_18BIT: 0x66,
106
+ }
107
+
108
+
109
+ def validate_dimension(rounding):
110
+ def validator(value):
111
+ value = cv.positive_int(value)
112
+ if value % rounding != 0:
113
+ raise cv.Invalid(f"Dimensions and offsets must be divisible by {rounding}")
114
+ return value
115
+
116
+ return validator
117
+
118
+
119
+ def map_sequence(value):
120
+ """
121
+ The format is a repeated sequence of [CMD, <data>] where <data> is s a sequence of bytes. The length is inferred
122
+ from the length of the sequence and should not be explicit.
123
+ A delay can be inserted by specifying "- delay N" where N is in ms
124
+ """
125
+ if isinstance(value, str) and value.lower().startswith("delay "):
126
+ value = value.lower()[6:]
127
+ delay = cv.All(
128
+ cv.positive_time_period_milliseconds,
129
+ cv.Range(TimePeriod(milliseconds=1), TimePeriod(milliseconds=255)),
130
+ )(value)
131
+ return DELAY_FLAG, delay.total_milliseconds
132
+ if isinstance(value, int):
133
+ return (value,)
134
+ value = cv.All(cv.ensure_list(cv.int_range(0, 255)), cv.Length(1, 254))(value)
135
+ return tuple(value)
136
+
137
+
138
+ def power_of_two(value):
139
+ value = cv.int_range(1, 128)(value)
140
+ if value & (value - 1) != 0:
141
+ raise cv.Invalid("value must be a power of two")
142
+ return value
143
+
144
+
145
+ def dimension_schema(rounding):
146
+ return cv.Any(
147
+ cv.dimensions,
148
+ cv.Schema(
149
+ {
150
+ cv.Required(CONF_WIDTH): validate_dimension(rounding),
151
+ cv.Required(CONF_HEIGHT): validate_dimension(rounding),
152
+ cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension(
153
+ rounding
154
+ ),
155
+ cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension(rounding),
156
+ }
157
+ ),
158
+ )
159
+
160
+
161
+ def model_schema(bus_mode, model: DriverChip, swapsies: bool):
162
+ transform = cv.Schema(
163
+ {
164
+ cv.Required(CONF_MIRROR_X): cv.boolean,
165
+ cv.Required(CONF_MIRROR_Y): cv.boolean,
166
+ }
167
+ )
168
+ if model.get_default(CONF_SWAP_XY, False) == cv.UNDEFINED:
169
+ transform = transform.extend(
170
+ {
171
+ cv.Optional(CONF_SWAP_XY): cv.invalid(
172
+ "Axis swapping not supported by this model"
173
+ )
174
+ }
175
+ )
176
+ else:
177
+ transform = transform.extend(
178
+ {
179
+ cv.Required(CONF_SWAP_XY): cv.boolean,
180
+ }
181
+ )
182
+ # CUSTOM model will need to provide a custom init sequence
183
+ iseqconf = (
184
+ cv.Required(CONF_INIT_SEQUENCE)
185
+ if model.initsequence is None
186
+ else cv.Optional(CONF_INIT_SEQUENCE)
187
+ )
188
+ # Dimensions are optional if the model has a default width and the transform is not overridden
189
+ cv_dimensions = (
190
+ cv.Optional if model.get_default(CONF_WIDTH) and not swapsies else cv.Required
191
+ )
192
+ pixel_modes = PIXEL_MODES if bus_mode == TYPE_SINGLE else (PIXEL_MODE_16BIT,)
193
+ color_depth = (
194
+ ("16", "8", "16bit", "8bit") if bus_mode == TYPE_SINGLE else ("16", "16bit")
195
+ )
196
+ schema = (
197
+ display.FULL_DISPLAY_SCHEMA.extend(
198
+ spi.spi_device_schema(
199
+ cs_pin_required=False,
200
+ default_mode="MODE3" if bus_mode == TYPE_OCTAL else "MODE0",
201
+ default_data_rate=model.get_default(CONF_DATA_RATE, 10_000_000),
202
+ mode=bus_mode,
203
+ )
204
+ )
205
+ .extend(
206
+ {
207
+ model.option(pin, cv.UNDEFINED): pins.gpio_output_pin_schema
208
+ for pin in (CONF_RESET_PIN, CONF_CS_PIN, CONF_DC_PIN)
209
+ }
210
+ )
211
+ .extend(
212
+ {
213
+ cv.GenerateID(): cv.declare_id(MipiSpi),
214
+ cv_dimensions(CONF_DIMENSIONS): dimension_schema(
215
+ model.get_default(CONF_DRAW_ROUNDING, 1)
216
+ ),
217
+ model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
218
+ pins.gpio_output_pin_schema
219
+ ),
220
+ model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(
221
+ COLOR_ORDERS, upper=True
222
+ ),
223
+ model.option(CONF_COLOR_DEPTH, 16): cv.one_of(*color_depth, lower=True),
224
+ model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
225
+ model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.Any(
226
+ cv.one_of(*pixel_modes, lower=True),
227
+ cv.int_range(0, 255, min_included=True, max_included=True),
228
+ ),
229
+ cv.Optional(CONF_TRANSFORM): transform,
230
+ cv.Optional(CONF_BUS_MODE, default=bus_mode): cv.one_of(
231
+ bus_mode, lower=True
232
+ ),
233
+ cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
234
+ iseqconf: cv.ensure_list(map_sequence),
235
+ }
236
+ )
237
+ .extend(
238
+ {
239
+ model.option(x): cv.boolean
240
+ for x in [
241
+ CONF_DRAW_FROM_ORIGIN,
242
+ CONF_SPI_16,
243
+ CONF_INVERT_COLORS,
244
+ CONF_USE_AXIS_FLIPS,
245
+ ]
246
+ }
247
+ )
248
+ )
249
+ if brightness := model.get_default(CONF_BRIGHTNESS):
250
+ schema = schema.extend(
251
+ {
252
+ cv.Optional(CONF_BRIGHTNESS, default=brightness): cv.int_range(
253
+ 0, 0xFF, min_included=True, max_included=True
254
+ ),
255
+ }
256
+ )
257
+ if bus_mode != TYPE_SINGLE:
258
+ return cv.All(schema, cv.only_with_esp_idf)
259
+ return schema
260
+
261
+
262
+ def rotation_as_transform(model, config):
263
+ """
264
+ Check if a rotation can be implemented in hardware using the MADCTL register.
265
+ A rotation of 180 is always possible, 90 and 270 are possible if the model supports swapping X and Y.
266
+ """
267
+ rotation = config.get(CONF_ROTATION, 0)
268
+ return rotation and (
269
+ model.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180
270
+ )
271
+
272
+
273
+ def config_schema(config):
274
+ # First get the model and bus mode
275
+ config = cv.Schema(
276
+ {
277
+ cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
278
+ },
279
+ extra=ALLOW_EXTRA,
280
+ )(config)
281
+ model = MODELS[config[CONF_MODEL]]
282
+ bus_modes = model.modes
283
+ config = cv.Schema(
284
+ {
285
+ model.option(CONF_BUS_MODE, TYPE_SINGLE): cv.one_of(*bus_modes, lower=True),
286
+ cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
287
+ },
288
+ extra=ALLOW_EXTRA,
289
+ )(config)
290
+ bus_mode = config.get(CONF_BUS_MODE, model.modes[0])
291
+ swapsies = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY) is True
292
+ config = model_schema(bus_mode, model, swapsies)(config)
293
+ # Check for invalid combinations of MADCTL config
294
+ if init_sequence := config.get(CONF_INIT_SEQUENCE):
295
+ if MADCTL in [x[0] for x in init_sequence] and CONF_TRANSFORM in config:
296
+ raise cv.Invalid(
297
+ f"transform is not supported when MADCTL ({MADCTL:#X}) is in the init sequence"
298
+ )
299
+
300
+ if bus_mode == TYPE_QUAD and CONF_DC_PIN in config:
301
+ raise cv.Invalid("DC pin is not supported in quad mode")
302
+ if config[CONF_PIXEL_MODE] == PIXEL_MODE_18BIT and bus_mode != TYPE_SINGLE:
303
+ raise cv.Invalid("18-bit pixel mode is not supported on a quad or octal bus")
304
+ if bus_mode != TYPE_QUAD and CONF_DC_PIN not in config:
305
+ raise cv.Invalid(f"DC pin is required in {bus_mode} mode")
306
+ return config
307
+
308
+
309
+ CONFIG_SCHEMA = config_schema
310
+
311
+
312
+ def get_transform(model, config):
313
+ can_transform = rotation_as_transform(model, config)
314
+ transform = config.get(
315
+ CONF_TRANSFORM,
316
+ {
317
+ CONF_MIRROR_X: model.get_default(CONF_MIRROR_X, False),
318
+ CONF_MIRROR_Y: model.get_default(CONF_MIRROR_Y, False),
319
+ CONF_SWAP_XY: model.get_default(CONF_SWAP_XY, False),
320
+ },
321
+ )
322
+
323
+ # Can we use the MADCTL register to set the rotation?
324
+ if can_transform and CONF_TRANSFORM not in config:
325
+ rotation = config[CONF_ROTATION]
326
+ if rotation == 180:
327
+ transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
328
+ transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
329
+ elif rotation == 90:
330
+ transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
331
+ transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
332
+ else:
333
+ transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
334
+ transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
335
+ transform[CONF_TRANSFORM] = True
336
+ return transform
337
+
338
+
339
+ def get_sequence(model, config):
340
+ """
341
+ Create the init sequence for the display.
342
+ Use the default sequence from the model, if any, and append any custom sequence provided in the config.
343
+ Append SLPOUT (if not already in the sequence) and DISPON to the end of the sequence
344
+ Pixel format, color order, and orientation will be set.
345
+ """
346
+ sequence = list(model.initsequence)
347
+ custom_sequence = config.get(CONF_INIT_SEQUENCE, [])
348
+ sequence.extend(custom_sequence)
349
+ # Ensure each command is a tuple
350
+ sequence = [x if isinstance(x, tuple) else (x,) for x in sequence]
351
+ commands = [x[0] for x in sequence]
352
+ # Set pixel format if not already in the custom sequence
353
+ if PIXFMT not in commands:
354
+ pixel_mode = config[CONF_PIXEL_MODE]
355
+ if not isinstance(pixel_mode, int):
356
+ pixel_mode = PIXEL_MODES[pixel_mode]
357
+ sequence.append((PIXFMT, pixel_mode))
358
+ # Does the chip use the flipping bits for mirroring rather than the reverse order bits?
359
+ use_flip = config[CONF_USE_AXIS_FLIPS]
360
+ if MADCTL not in commands:
361
+ madctl = 0
362
+ transform = get_transform(model, config)
363
+ if transform.get(CONF_TRANSFORM):
364
+ LOGGER.info("Using hardware transform to implement rotation")
365
+ if transform.get(CONF_MIRROR_X):
366
+ madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
367
+ if transform.get(CONF_MIRROR_Y):
368
+ madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
369
+ if transform.get(CONF_SWAP_XY) is True: # Exclude Undefined
370
+ madctl |= MADCTL_MV
371
+ if config[CONF_COLOR_ORDER] == MODE_BGR:
372
+ madctl |= MADCTL_BGR
373
+ sequence.append((MADCTL, madctl))
374
+ if INVON not in commands and INVOFF not in commands:
375
+ if config[CONF_INVERT_COLORS]:
376
+ sequence.append((INVON,))
377
+ else:
378
+ sequence.append((INVOFF,))
379
+ if BRIGHTNESS not in commands:
380
+ if brightness := config.get(
381
+ CONF_BRIGHTNESS, model.get_default(CONF_BRIGHTNESS)
382
+ ):
383
+ sequence.append((BRIGHTNESS, brightness))
384
+ if SLPOUT not in commands:
385
+ sequence.append((SLPOUT,))
386
+ sequence.append((DISPON,))
387
+
388
+ # Flatten the sequence into a list of bytes, with the length of each command
389
+ # or the delay flag inserted where needed
390
+ return sum(
391
+ tuple(
392
+ (x[1], 0xFF) if x[0] == DELAY_FLAG else (x[0], len(x) - 1) + x[1:]
393
+ for x in sequence
394
+ ),
395
+ (),
396
+ )
397
+
398
+
399
+ async def to_code(config):
400
+ model = MODELS[config[CONF_MODEL]]
401
+ transform = get_transform(model, config)
402
+ if CONF_DIMENSIONS in config:
403
+ # Explicit dimensions, just use as is
404
+ dimensions = config[CONF_DIMENSIONS]
405
+ if isinstance(dimensions, dict):
406
+ width = dimensions[CONF_WIDTH]
407
+ height = dimensions[CONF_HEIGHT]
408
+ offset_width = dimensions[CONF_OFFSET_WIDTH]
409
+ offset_height = dimensions[CONF_OFFSET_HEIGHT]
410
+ else:
411
+ (width, height) = dimensions
412
+ offset_width = 0
413
+ offset_height = 0
414
+ else:
415
+ # Default dimensions, use model defaults and transform if needed
416
+ width = model.get_default(CONF_WIDTH)
417
+ height = model.get_default(CONF_HEIGHT)
418
+ offset_width = model.get_default(CONF_OFFSET_WIDTH, 0)
419
+ offset_height = model.get_default(CONF_OFFSET_HEIGHT, 0)
420
+
421
+ # if mirroring axes and there are offsets, also mirror the offsets to cater for situations where
422
+ # the offset is asymmetric
423
+ if transform[CONF_MIRROR_X]:
424
+ native_width = model.get_default(
425
+ CONF_NATIVE_WIDTH, width + offset_width * 2
426
+ )
427
+ offset_width = native_width - width - offset_width
428
+ if transform[CONF_MIRROR_Y]:
429
+ native_height = model.get_default(
430
+ CONF_NATIVE_HEIGHT, height + offset_height * 2
431
+ )
432
+ offset_height = native_height - height - offset_height
433
+ # Swap default dimensions if swap_xy is set
434
+ if transform[CONF_SWAP_XY] is True:
435
+ width, height = height, width
436
+ offset_height, offset_width = offset_width, offset_height
437
+
438
+ color_depth = config[CONF_COLOR_DEPTH]
439
+ if color_depth.endswith("bit"):
440
+ color_depth = color_depth[:-3]
441
+ color_depth = COLOR_DEPTHS[int(color_depth)]
442
+
443
+ var = cg.new_Pvariable(
444
+ config[CONF_ID], width, height, offset_width, offset_height, color_depth
445
+ )
446
+ cg.add(var.set_init_sequence(get_sequence(model, config)))
447
+ if rotation_as_transform(model, config):
448
+ if CONF_TRANSFORM in config:
449
+ LOGGER.warning("Use of 'transform' with 'rotation' is not recommended")
450
+ else:
451
+ config[CONF_ROTATION] = 0
452
+ cg.add(var.set_model(config[CONF_MODEL]))
453
+ cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN]))
454
+ cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING]))
455
+ cg.add(var.set_spi_16(config[CONF_SPI_16]))
456
+ if enable_pin := config.get(CONF_ENABLE_PIN):
457
+ enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
458
+ cg.add(var.set_enable_pins(enable))
459
+
460
+ if reset_pin := config.get(CONF_RESET_PIN):
461
+ reset = await cg.gpio_pin_expression(reset_pin)
462
+ cg.add(var.set_reset_pin(reset))
463
+
464
+ if dc_pin := config.get(CONF_DC_PIN):
465
+ dc_pin = await cg.gpio_pin_expression(dc_pin)
466
+ cg.add(var.set_dc_pin(dc_pin))
467
+
468
+ if lamb := config.get(CONF_LAMBDA):
469
+ lambda_ = await cg.process_lambda(
470
+ lamb, [(display.DisplayRef, "it")], return_type=cg.void
471
+ )
472
+ cg.add(var.set_writer(lambda_))
473
+ await display.register_display(var, config)
474
+ await spi.register_spi_device(var, config)