esphome 2025.8.4__py3-none-any.whl → 2025.9.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. esphome/__main__.py +36 -42
  2. esphome/components/absolute_humidity/absolute_humidity.cpp +3 -5
  3. esphome/components/adc/adc_sensor_esp32.cpp +29 -6
  4. esphome/components/ags10/ags10.cpp +3 -18
  5. esphome/components/ags10/ags10.h +2 -12
  6. esphome/components/aht10/aht10.cpp +3 -3
  7. esphome/components/airthings_ble/__init__.py +2 -2
  8. esphome/components/alarm_control_panel/__init__.py +2 -2
  9. esphome/components/am2315c/am2315c.cpp +1 -17
  10. esphome/components/am2315c/am2315c.h +2 -3
  11. esphome/components/api/__init__.py +2 -2
  12. esphome/components/api/api_connection.cpp +34 -23
  13. esphome/components/api/api_connection.h +20 -39
  14. esphome/components/api/api_frame_helper.cpp +25 -25
  15. esphome/components/api/api_frame_helper.h +3 -3
  16. esphome/components/api/api_frame_helper_noise.cpp +75 -40
  17. esphome/components/api/api_frame_helper_noise.h +3 -7
  18. esphome/components/api/api_frame_helper_plaintext.cpp +17 -4
  19. esphome/components/api/api_frame_helper_plaintext.h +1 -4
  20. esphome/components/api/api_pb2.cpp +20 -2
  21. esphome/components/api/api_pb2.h +146 -141
  22. esphome/components/api/api_pb2_dump.cpp +12 -1
  23. esphome/components/api/proto.cpp +33 -37
  24. esphome/components/async_tcp/__init__.py +2 -2
  25. esphome/components/atm90e26/sensor.py +2 -0
  26. esphome/components/atm90e32/sensor.py +4 -2
  27. esphome/components/audio_adc/__init__.py +2 -2
  28. esphome/components/audio_dac/__init__.py +2 -2
  29. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +1 -1
  30. esphome/components/bedjet/bedjet_hub.cpp +1 -1
  31. esphome/components/binary_sensor/__init__.py +2 -2
  32. esphome/components/binary_sensor/binary_sensor.cpp +13 -0
  33. esphome/components/binary_sensor/binary_sensor.h +4 -7
  34. esphome/components/bl0940/__init__.py +6 -1
  35. esphome/components/bl0940/bl0940.cpp +178 -41
  36. esphome/components/bl0940/bl0940.h +121 -76
  37. esphome/components/bl0940/button/__init__.py +27 -0
  38. esphome/components/bl0940/button/calibration_reset_button.cpp +20 -0
  39. esphome/components/bl0940/button/calibration_reset_button.h +19 -0
  40. esphome/components/bl0940/number/__init__.py +94 -0
  41. esphome/components/bl0940/number/calibration_number.cpp +29 -0
  42. esphome/components/bl0940/number/calibration_number.h +26 -0
  43. esphome/components/bl0940/sensor.py +151 -2
  44. esphome/components/bl0942/bl0942.cpp +1 -1
  45. esphome/components/ble_client/output/__init__.py +4 -4
  46. esphome/components/bluetooth_proxy/__init__.py +1 -1
  47. esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -1
  48. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +15 -7
  49. esphome/components/bluetooth_proxy/bluetooth_proxy.h +3 -2
  50. esphome/components/button/__init__.py +2 -2
  51. esphome/components/button/button.cpp +13 -0
  52. esphome/components/button/button.h +4 -7
  53. esphome/components/camera/buffer.h +18 -0
  54. esphome/components/camera/buffer_impl.cpp +20 -0
  55. esphome/components/camera/buffer_impl.h +26 -0
  56. esphome/components/camera/camera.h +43 -0
  57. esphome/components/camera/encoder.h +69 -0
  58. esphome/components/camera_encoder/__init__.py +62 -0
  59. esphome/components/camera_encoder/encoder_buffer_impl.cpp +23 -0
  60. esphome/components/camera_encoder/encoder_buffer_impl.h +25 -0
  61. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +82 -0
  62. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +39 -0
  63. esphome/components/captive_portal/__init__.py +2 -2
  64. esphome/components/captive_portal/captive_portal.cpp +35 -12
  65. esphome/components/captive_portal/captive_portal.h +3 -3
  66. esphome/components/ccs811/ccs811.cpp +3 -3
  67. esphome/components/climate/__init__.py +2 -2
  68. esphome/components/climate/climate.cpp +1 -1
  69. esphome/components/cover/__init__.py +5 -5
  70. esphome/components/cover/cover.cpp +1 -1
  71. esphome/components/cover/cover.h +2 -2
  72. esphome/components/dallas_temp/dallas_temp.cpp +2 -2
  73. esphome/components/datetime/__init__.py +2 -2
  74. esphome/components/datetime/date_entity.h +2 -2
  75. esphome/components/datetime/datetime_entity.h +2 -2
  76. esphome/components/datetime/time_entity.h +2 -2
  77. esphome/components/debug/debug_esp32.cpp +1 -1
  78. esphome/components/display/__init__.py +4 -4
  79. esphome/components/duty_time/duty_time_sensor.cpp +1 -1
  80. esphome/components/esp32/__init__.py +0 -5
  81. esphome/components/esp32/gpio.cpp +27 -23
  82. esphome/components/esp32/gpio.h +26 -11
  83. esphome/components/esp32/preferences.cpp +8 -4
  84. esphome/components/esp32_ble/__init__.py +7 -2
  85. esphome/components/esp32_ble_client/ble_client_base.cpp +7 -3
  86. esphome/components/esp32_ble_tracker/__init__.py +2 -2
  87. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +9 -44
  88. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -14
  89. esphome/components/esp8266/__init__.py +2 -2
  90. esphome/components/esp8266/core.cpp +2 -2
  91. esphome/components/esp8266/gpio.py +4 -4
  92. esphome/components/esp8266/preferences.cpp +30 -28
  93. esphome/components/esphome/ota/__init__.py +2 -2
  94. esphome/components/esphome/ota/ota_esphome.cpp +21 -19
  95. esphome/components/esphome/ota/ota_esphome.h +6 -5
  96. esphome/components/ethernet/__init__.py +7 -2
  97. esphome/components/ethernet/ethernet_component.cpp +1 -1
  98. esphome/components/event/__init__.py +2 -2
  99. esphome/components/event/event.h +4 -4
  100. esphome/components/fan/__init__.py +2 -2
  101. esphome/components/fan/fan.cpp +2 -1
  102. esphome/components/gdk101/gdk101.cpp +4 -4
  103. esphome/components/globals/__init__.py +2 -2
  104. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +19 -18
  105. esphome/components/gpio_expander/cached_gpio.h +36 -16
  106. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -5
  107. esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
  108. esphome/components/haier/haier_base.cpp +1 -1
  109. esphome/components/haier/hon_climate.cpp +1 -1
  110. esphome/components/hlw8012/hlw8012.cpp +5 -5
  111. esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +4 -4
  112. esphome/components/host/preferences.h +3 -2
  113. esphome/components/hte501/hte501.cpp +3 -21
  114. esphome/components/hte501/hte501.h +2 -3
  115. esphome/components/http_request/ota/__init__.py +2 -2
  116. esphome/components/i2c/__init__.py +2 -2
  117. esphome/components/i2c/i2c.cpp +13 -9
  118. esphome/components/i2c/i2c_bus.h +36 -6
  119. esphome/components/i2s_audio/__init__.py +8 -2
  120. esphome/components/i2s_audio/media_player/__init__.py +1 -1
  121. esphome/components/i2s_audio/microphone/__init__.py +1 -1
  122. esphome/components/i2s_audio/speaker/__init__.py +1 -1
  123. esphome/components/inkplate/__init__.py +1 -0
  124. esphome/components/inkplate/const.py +105 -0
  125. esphome/components/inkplate/display.py +238 -0
  126. esphome/components/{inkplate6 → inkplate}/inkplate.cpp +156 -74
  127. esphome/components/{inkplate6 → inkplate}/inkplate.h +28 -68
  128. esphome/components/inkplate6/__init__.py +0 -1
  129. esphome/components/inkplate6/display.py +2 -211
  130. esphome/components/integration/integration_sensor.cpp +1 -1
  131. esphome/components/json/__init__.py +2 -2
  132. esphome/components/lc709203f/lc709203f.cpp +4 -17
  133. esphome/components/lc709203f/lc709203f.h +2 -3
  134. esphome/components/ld2420/text_sensor/{text_sensor.cpp → ld2420_text_sensor.cpp} +1 -1
  135. esphome/components/ld2450/ld2450.cpp +1 -1
  136. esphome/components/libretiny/preferences.cpp +13 -5
  137. esphome/components/light/__init__.py +2 -2
  138. esphome/components/light/addressable_light_effect.h +7 -0
  139. esphome/components/light/base_light_effects.h +8 -0
  140. esphome/components/light/light_call.cpp +22 -20
  141. esphome/components/light/light_effect.cpp +36 -0
  142. esphome/components/light/light_effect.h +14 -0
  143. esphome/components/light/light_json_schema.cpp +9 -1
  144. esphome/components/light/light_state.cpp +2 -2
  145. esphome/components/light/light_state.h +38 -0
  146. esphome/components/lock/__init__.py +2 -2
  147. esphome/components/lock/lock.h +2 -2
  148. esphome/components/logger/__init__.py +2 -2
  149. esphome/components/logger/logger.cpp +25 -4
  150. esphome/components/logger/logger.h +1 -1
  151. esphome/components/logger/logger_esp32.cpp +16 -8
  152. esphome/components/logger/logger_esp8266.cpp +11 -3
  153. esphome/components/logger/logger_libretiny.cpp +13 -3
  154. esphome/components/logger/logger_rp2040.cpp +14 -3
  155. esphome/components/logger/logger_zephyr.cpp +15 -4
  156. esphome/components/lvgl/defines.py +1 -0
  157. esphome/components/lvgl/hello_world.py +96 -33
  158. esphome/components/lvgl/number/lvgl_number.h +1 -1
  159. esphome/components/lvgl/select/lvgl_select.h +1 -1
  160. esphome/components/lvgl/widgets/__init__.py +0 -1
  161. esphome/components/lvgl/widgets/spinbox.py +20 -11
  162. esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp +1 -1
  163. esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp +1 -1
  164. esphome/components/mapping/__init__.py +13 -5
  165. esphome/components/mapping/mapping.h +69 -0
  166. esphome/components/max17043/max17043.cpp +2 -2
  167. esphome/components/mcp23016/__init__.py +1 -0
  168. esphome/components/mcp23016/mcp23016.cpp +20 -5
  169. esphome/components/mcp23016/mcp23016.h +10 -4
  170. esphome/components/mcp23x08_base/mcp23x08_base.cpp +1 -1
  171. esphome/components/mcp23x17_base/mcp23x17_base.cpp +2 -2
  172. esphome/components/mdns/__init__.py +2 -2
  173. esphome/components/mdns/mdns_component.cpp +145 -54
  174. esphome/components/media_player/__init__.py +2 -2
  175. esphome/components/micro_wake_word/__init__.py +2 -2
  176. esphome/components/microphone/__init__.py +2 -2
  177. esphome/components/mipi/__init__.py +77 -33
  178. esphome/components/mipi_rgb/__init__.py +2 -0
  179. esphome/components/mipi_rgb/display.py +321 -0
  180. esphome/components/mipi_rgb/mipi_rgb.cpp +388 -0
  181. esphome/components/mipi_rgb/mipi_rgb.h +127 -0
  182. esphome/components/mipi_rgb/models/guition.py +24 -0
  183. esphome/components/mipi_rgb/models/lilygo.py +228 -0
  184. esphome/components/mipi_rgb/models/rpi.py +9 -0
  185. esphome/components/mipi_rgb/models/st7701s.py +214 -0
  186. esphome/components/mipi_rgb/models/waveshare.py +64 -0
  187. esphome/components/mipi_spi/models/jc.py +229 -0
  188. esphome/components/mlx90614/mlx90614.cpp +1 -16
  189. esphome/components/mlx90614/mlx90614.h +0 -1
  190. esphome/components/mqtt/__init__.py +2 -2
  191. esphome/components/mqtt/mqtt_sensor.cpp +7 -2
  192. esphome/components/ms5611/ms5611.cpp +7 -6
  193. esphome/components/network/__init__.py +2 -2
  194. esphome/components/nextion/nextion_upload.cpp +4 -1
  195. esphome/components/nrf52/__init__.py +49 -6
  196. esphome/components/nrf52/const.py +1 -0
  197. esphome/components/nrf52/dfu.cpp +51 -0
  198. esphome/components/nrf52/dfu.h +24 -0
  199. esphome/components/ntc/ntc.cpp +1 -1
  200. esphome/components/number/__init__.py +2 -2
  201. esphome/components/number/automation.cpp +1 -1
  202. esphome/components/number/number.cpp +21 -0
  203. esphome/components/number/number.h +4 -13
  204. esphome/components/opentherm/hub.h +6 -6
  205. esphome/components/opentherm/number/{number.cpp → opentherm_number.cpp} +2 -2
  206. esphome/components/opentherm/output/{output.cpp → opentherm_output.cpp} +1 -1
  207. esphome/components/opentherm/switch/{switch.cpp → opentherm_switch.cpp} +1 -1
  208. esphome/components/ota/__init__.py +2 -2
  209. esphome/components/pca6416a/__init__.py +1 -0
  210. esphome/components/pca6416a/pca6416a.cpp +20 -5
  211. esphome/components/pca6416a/pca6416a.h +12 -5
  212. esphome/components/pca9554/__init__.py +2 -1
  213. esphome/components/pca9554/pca9554.cpp +12 -18
  214. esphome/components/pca9554/pca9554.h +10 -9
  215. esphome/components/pcf8574/__init__.py +1 -0
  216. esphome/components/pcf8574/pcf8574.cpp +14 -5
  217. esphome/components/pcf8574/pcf8574.h +13 -6
  218. esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +7 -7
  219. esphome/components/pipsolar/__init__.py +3 -3
  220. esphome/components/pipsolar/output/__init__.py +4 -4
  221. esphome/components/pulse_width/pulse_width.cpp +2 -2
  222. esphome/components/qmp6988/qmp6988.cpp +81 -126
  223. esphome/components/qmp6988/qmp6988.h +31 -37
  224. esphome/components/radon_eye_ble/__init__.py +2 -2
  225. esphome/components/remote_base/__init__.py +6 -8
  226. esphome/components/rotary_encoder/rotary_encoder.cpp +1 -1
  227. esphome/components/rp2040/__init__.py +2 -2
  228. esphome/components/runtime_stats/runtime_stats.cpp +10 -23
  229. esphome/components/runtime_stats/runtime_stats.h +4 -10
  230. esphome/components/safe_mode/__init__.py +2 -2
  231. esphome/components/safe_mode/safe_mode.cpp +33 -31
  232. esphome/components/script/script.cpp +6 -0
  233. esphome/components/script/script.h +19 -5
  234. esphome/components/sdm_meter/sensor.py +3 -1
  235. esphome/components/select/__init__.py +2 -2
  236. esphome/components/select/select.h +2 -2
  237. esphome/components/sen5x/sen5x.cpp +57 -55
  238. esphome/components/sen5x/sen5x.h +21 -15
  239. esphome/components/sen5x/sensor.py +67 -44
  240. esphome/components/sensirion_common/i2c_sensirion.cpp +18 -47
  241. esphome/components/sensirion_common/i2c_sensirion.h +39 -55
  242. esphome/components/sensor/__init__.py +2 -2
  243. esphome/components/sensor/automation.h +1 -1
  244. esphome/components/sensor/sensor.cpp +34 -6
  245. esphome/components/sensor/sensor.h +4 -21
  246. esphome/components/sgp30/sgp30.cpp +34 -35
  247. esphome/components/sgp30/sgp30.h +11 -10
  248. esphome/components/sgp4x/sgp4x.cpp +2 -2
  249. esphome/components/shelly_dimmer/light.py +7 -7
  250. esphome/components/sht4x/sht4x.cpp +1 -1
  251. esphome/components/sntp/sntp_component.cpp +36 -9
  252. esphome/components/sntp/sntp_component.h +7 -0
  253. esphome/components/sound_level/sound_level.cpp +1 -1
  254. esphome/components/speaker/__init__.py +2 -2
  255. esphome/components/speaker/media_player/__init__.py +2 -2
  256. esphome/components/speaker/media_player/speaker_media_player.cpp +1 -1
  257. esphome/components/spi/__init__.py +2 -2
  258. esphome/components/sprinkler/sprinkler.cpp +1 -1
  259. esphome/components/sps30/sps30.cpp +18 -23
  260. esphome/components/sps30/sps30.h +3 -3
  261. esphome/components/status_led/__init__.py +2 -2
  262. esphome/components/stepper/__init__.py +2 -2
  263. esphome/components/switch/__init__.py +2 -2
  264. esphome/components/switch/switch.cpp +5 -5
  265. esphome/components/sx1509/__init__.py +1 -1
  266. esphome/components/sx1509/sx1509.cpp +12 -7
  267. esphome/components/sx1509/sx1509.h +11 -4
  268. esphome/components/tca9555/tca9555.cpp +5 -5
  269. esphome/components/tee501/tee501.cpp +2 -21
  270. esphome/components/tee501/tee501.h +2 -4
  271. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +1 -1
  272. esphome/components/template/datetime/template_date.cpp +1 -1
  273. esphome/components/template/datetime/template_datetime.cpp +2 -2
  274. esphome/components/template/datetime/template_time.cpp +1 -1
  275. esphome/components/template/number/template_number.cpp +1 -1
  276. esphome/components/template/select/template_select.cpp +1 -1
  277. esphome/components/template/text/template_text.cpp +1 -1
  278. esphome/components/text/__init__.py +2 -2
  279. esphome/components/text/text.h +2 -2
  280. esphome/components/text_sensor/__init__.py +2 -2
  281. esphome/components/text_sensor/text_sensor.h +4 -4
  282. esphome/components/thermostat/climate.py +11 -7
  283. esphome/components/thermostat/thermostat_climate.cpp +237 -206
  284. esphome/components/thermostat/thermostat_climate.h +52 -41
  285. esphome/components/time/__init__.py +2 -2
  286. esphome/components/tmp1075/tmp1075.cpp +1 -1
  287. esphome/components/total_daily_energy/total_daily_energy.cpp +1 -1
  288. esphome/components/touchscreen/__init__.py +2 -2
  289. esphome/components/tuya/number/tuya_number.cpp +1 -1
  290. esphome/components/udp/udp_component.cpp +3 -3
  291. esphome/components/ufire_ec/ufire_ec.cpp +4 -4
  292. esphome/components/ufire_ise/ufire_ise.cpp +4 -4
  293. esphome/components/update/__init__.py +2 -2
  294. esphome/components/usb_uart/usb_uart.cpp +1 -1
  295. esphome/components/valve/__init__.py +5 -5
  296. esphome/components/valve/valve.cpp +1 -1
  297. esphome/components/valve/valve.h +2 -2
  298. esphome/components/wake_on_lan/wake_on_lan.cpp +2 -2
  299. esphome/components/waveshare_epaper/waveshare_213v3.cpp +1 -1
  300. esphome/components/web_server/__init__.py +2 -2
  301. esphome/components/web_server/ota/__init__.py +2 -2
  302. esphome/components/web_server/ota/ota_web_server.cpp +11 -0
  303. esphome/components/web_server/web_server.cpp +58 -12
  304. esphome/components/web_server_base/__init__.py +2 -2
  305. esphome/components/wifi/__init__.py +5 -5
  306. esphome/components/wifi/wifi_component.cpp +3 -3
  307. esphome/components/wifi/wifi_component_esp_idf.cpp +2 -0
  308. esphome/config_validation.py +2 -2
  309. esphome/const.py +2 -1
  310. esphome/core/__init__.py +1 -0
  311. esphome/core/application.cpp +89 -51
  312. esphome/core/application.h +1 -0
  313. esphome/core/component.cpp +41 -19
  314. esphome/core/component.h +17 -13
  315. esphome/core/config.py +7 -7
  316. esphome/core/defines.h +4 -0
  317. esphome/core/entity_base.cpp +22 -8
  318. esphome/core/entity_base.h +43 -0
  319. esphome/core/helpers.cpp +26 -13
  320. esphome/core/helpers.h +4 -3
  321. esphome/core/ring_buffer.cpp +6 -2
  322. esphome/core/ring_buffer.h +2 -1
  323. esphome/core/scheduler.cpp +175 -94
  324. esphome/core/scheduler.h +66 -35
  325. esphome/core/time.cpp +6 -20
  326. esphome/coroutine.py +80 -3
  327. esphome/cpp_generator.py +13 -0
  328. esphome/cpp_helpers.py +2 -2
  329. esphome/dashboard/web_server.py +67 -10
  330. esphome/espota2.py +13 -6
  331. esphome/helpers.py +68 -83
  332. esphome/resolver.py +67 -0
  333. esphome/util.py +9 -6
  334. esphome/wizard.py +39 -26
  335. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/METADATA +9 -9
  336. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/RECORD +344 -313
  337. /esphome/components/ld2420/text_sensor/{text_sensor.h → ld2420_text_sensor.h} +0 -0
  338. /esphome/components/opentherm/number/{number.h → opentherm_number.h} +0 -0
  339. /esphome/components/opentherm/output/{output.h → opentherm_output.h} +0 -0
  340. /esphome/components/opentherm/switch/{switch.h → opentherm_switch.h} +0 -0
  341. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/WHEEL +0 -0
  342. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/entry_points.txt +0 -0
  343. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/licenses/LICENSE +0 -0
  344. {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,321 @@
1
+ import importlib
2
+ import pkgutil
3
+
4
+ from esphome import pins
5
+ import esphome.codegen as cg
6
+ from esphome.components import display, spi
7
+ from esphome.components.const import (
8
+ BYTE_ORDER_BIG,
9
+ BYTE_ORDER_LITTLE,
10
+ CONF_BYTE_ORDER,
11
+ CONF_DRAW_ROUNDING,
12
+ )
13
+ from esphome.components.display import CONF_SHOW_TEST_CARD
14
+ from esphome.components.esp32 import const, only_on_variant
15
+ from esphome.components.mipi import (
16
+ COLOR_ORDERS,
17
+ CONF_DE_PIN,
18
+ CONF_HSYNC_BACK_PORCH,
19
+ CONF_HSYNC_FRONT_PORCH,
20
+ CONF_HSYNC_PULSE_WIDTH,
21
+ CONF_PCLK_PIN,
22
+ CONF_PIXEL_MODE,
23
+ CONF_USE_AXIS_FLIPS,
24
+ CONF_VSYNC_BACK_PORCH,
25
+ CONF_VSYNC_FRONT_PORCH,
26
+ CONF_VSYNC_PULSE_WIDTH,
27
+ MODE_BGR,
28
+ PIXEL_MODE_16BIT,
29
+ PIXEL_MODE_18BIT,
30
+ DriverChip,
31
+ dimension_schema,
32
+ map_sequence,
33
+ power_of_two,
34
+ requires_buffer,
35
+ )
36
+ from esphome.components.rpi_dpi_rgb.display import (
37
+ CONF_PCLK_FREQUENCY,
38
+ CONF_PCLK_INVERTED,
39
+ )
40
+ import esphome.config_validation as cv
41
+ from esphome.const import (
42
+ CONF_BLUE,
43
+ CONF_COLOR_ORDER,
44
+ CONF_CS_PIN,
45
+ CONF_DATA_PINS,
46
+ CONF_DATA_RATE,
47
+ CONF_DC_PIN,
48
+ CONF_DIMENSIONS,
49
+ CONF_ENABLE_PIN,
50
+ CONF_GREEN,
51
+ CONF_HSYNC_PIN,
52
+ CONF_ID,
53
+ CONF_IGNORE_STRAPPING_WARNING,
54
+ CONF_INIT_SEQUENCE,
55
+ CONF_INVERT_COLORS,
56
+ CONF_LAMBDA,
57
+ CONF_MIRROR_X,
58
+ CONF_MIRROR_Y,
59
+ CONF_MODEL,
60
+ CONF_NUMBER,
61
+ CONF_RED,
62
+ CONF_RESET_PIN,
63
+ CONF_ROTATION,
64
+ CONF_SPI_ID,
65
+ CONF_SWAP_XY,
66
+ CONF_TRANSFORM,
67
+ CONF_VSYNC_PIN,
68
+ CONF_WIDTH,
69
+ )
70
+ from esphome.final_validate import full_config
71
+
72
+ from ..spi import CONF_SPI_MODE, SPI_DATA_RATE_SCHEMA, SPI_MODE_OPTIONS, SPIComponent
73
+ from . import models
74
+
75
+ DEPENDENCIES = ["esp32", "psram"]
76
+
77
+ mipi_rgb_ns = cg.esphome_ns.namespace("mipi_rgb")
78
+ mipi_rgb = mipi_rgb_ns.class_("MipiRgb", display.Display, cg.Component)
79
+ mipi_rgb_spi = mipi_rgb_ns.class_(
80
+ "MipiRgbSpi", mipi_rgb, display.Display, cg.Component, spi.SPIDevice
81
+ )
82
+ ColorOrder = display.display_ns.enum("ColorMode")
83
+
84
+ DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
85
+
86
+ DriverChip("CUSTOM")
87
+
88
+ # Import all models dynamically from the models package
89
+
90
+ for module_info in pkgutil.iter_modules(models.__path__):
91
+ importlib.import_module(f".models.{module_info.name}", package=__package__)
92
+
93
+ MODELS = DriverChip.get_models()
94
+
95
+
96
+ def data_pin_validate(value):
97
+ """
98
+ It is safe to use strapping pins as RGB output data bits, as they are outputs only,
99
+ and not initialised until after boot.
100
+ """
101
+ if not isinstance(value, dict):
102
+ try:
103
+ return DATA_PIN_SCHEMA(
104
+ {CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True}
105
+ )
106
+ except cv.Invalid:
107
+ pass
108
+ return DATA_PIN_SCHEMA(value)
109
+
110
+
111
+ def data_pin_set(length):
112
+ return cv.All(
113
+ [data_pin_validate],
114
+ cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"),
115
+ )
116
+
117
+
118
+ def model_schema(config):
119
+ model = MODELS[config[CONF_MODEL].upper()]
120
+ if transforms := model.transforms:
121
+ transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms})
122
+ for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y):
123
+ if x not in transforms:
124
+ transform = transform.extend(
125
+ {cv.Optional(x): cv.invalid(f"{x} not supported by this model")}
126
+ )
127
+ else:
128
+ transform = cv.invalid("This model does not support transforms")
129
+
130
+ # RPI model does not use an init sequence, indicates with empty list
131
+ if model.initsequence is None:
132
+ # Custom model requires an init sequence
133
+ iseqconf = cv.Required(CONF_INIT_SEQUENCE)
134
+ uses_spi = True
135
+ else:
136
+ iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
137
+ uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
138
+ swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False)
139
+
140
+ # Dimensions are optional if the model has a default width and the swap_xy transform is not overridden
141
+ cv_dimensions = (
142
+ cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required
143
+ )
144
+ pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
145
+ schema = display.FULL_DISPLAY_SCHEMA.extend(
146
+ {
147
+ model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
148
+ cv.GenerateID(): cv.declare_id(mipi_rgb_spi if uses_spi else mipi_rgb),
149
+ cv_dimensions(CONF_DIMENSIONS): dimension_schema(
150
+ model.get_default(CONF_DRAW_ROUNDING, 1)
151
+ ),
152
+ model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
153
+ pins.gpio_output_pin_schema
154
+ ),
155
+ model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True),
156
+ model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
157
+ model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
158
+ *pixel_modes, lower=True
159
+ ),
160
+ model.option(CONF_TRANSFORM, cv.UNDEFINED): transform,
161
+ cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
162
+ model.option(CONF_INVERT_COLORS, False): cv.boolean,
163
+ model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
164
+ model.option(CONF_PCLK_FREQUENCY, "40MHz"): cv.All(
165
+ cv.frequency, cv.Range(min=4e6, max=100e6)
166
+ ),
167
+ model.option(CONF_PCLK_INVERTED, True): cv.boolean,
168
+ iseqconf: cv.ensure_list(map_sequence),
169
+ model.option(CONF_BYTE_ORDER, BYTE_ORDER_BIG): cv.one_of(
170
+ BYTE_ORDER_LITTLE, BYTE_ORDER_BIG, lower=True
171
+ ),
172
+ model.option(CONF_HSYNC_PULSE_WIDTH): cv.int_,
173
+ model.option(CONF_HSYNC_BACK_PORCH): cv.int_,
174
+ model.option(CONF_HSYNC_FRONT_PORCH): cv.int_,
175
+ model.option(CONF_VSYNC_PULSE_WIDTH): cv.int_,
176
+ model.option(CONF_VSYNC_BACK_PORCH): cv.int_,
177
+ model.option(CONF_VSYNC_FRONT_PORCH): cv.int_,
178
+ model.option(CONF_DATA_PINS): cv.Any(
179
+ data_pin_set(16),
180
+ cv.Schema(
181
+ {
182
+ cv.Required(CONF_RED): data_pin_set(5),
183
+ cv.Required(CONF_GREEN): data_pin_set(6),
184
+ cv.Required(CONF_BLUE): data_pin_set(5),
185
+ }
186
+ ),
187
+ ),
188
+ model.option(
189
+ CONF_DE_PIN, cv.UNDEFINED
190
+ ): pins.internal_gpio_output_pin_schema,
191
+ model.option(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema,
192
+ model.option(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema,
193
+ model.option(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema,
194
+ model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
195
+ }
196
+ )
197
+ if uses_spi:
198
+ schema = schema.extend(
199
+ {
200
+ cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
201
+ model.option(CONF_DC_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
202
+ model.option(CONF_DATA_RATE, "1MHz"): SPI_DATA_RATE_SCHEMA,
203
+ model.option(CONF_SPI_MODE, "MODE0"): cv.enum(
204
+ SPI_MODE_OPTIONS, upper=True
205
+ ),
206
+ model.option(CONF_CS_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema,
207
+ }
208
+ )
209
+ return schema
210
+
211
+
212
+ def _config_schema(config):
213
+ config = cv.Schema(
214
+ {
215
+ cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
216
+ },
217
+ extra=cv.ALLOW_EXTRA,
218
+ )(config)
219
+ schema = model_schema(config)
220
+ return cv.All(
221
+ schema,
222
+ only_on_variant(supported=[const.VARIANT_ESP32S3]),
223
+ cv.only_with_esp_idf,
224
+ )(config)
225
+
226
+
227
+ CONFIG_SCHEMA = _config_schema
228
+
229
+
230
+ def _final_validate(config):
231
+ global_config = full_config.get()
232
+
233
+ from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
234
+
235
+ if not requires_buffer(config) and LVGL_DOMAIN not in global_config:
236
+ # If no drawing methods are configured, and LVGL is not enabled, show a test card
237
+ config[CONF_SHOW_TEST_CARD] = True
238
+ if CONF_SPI_ID in config:
239
+ config = spi.final_validate_device_schema(
240
+ "mipi_rgb", require_miso=False, require_mosi=True
241
+ )(config)
242
+ return config
243
+
244
+
245
+ FINAL_VALIDATE_SCHEMA = _final_validate
246
+
247
+
248
+ async def to_code(config):
249
+ model = MODELS[config[CONF_MODEL].upper()]
250
+ width, height, _offset_width, _offset_height = model.get_dimensions(config)
251
+ var = cg.new_Pvariable(config[CONF_ID], width, height)
252
+ cg.add(var.set_model(model.name))
253
+ if enable_pin := config.get(CONF_ENABLE_PIN):
254
+ enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
255
+ cg.add(var.set_enable_pins(enable))
256
+
257
+ if CONF_SPI_ID in config:
258
+ await spi.register_spi_device(var, config)
259
+ sequence, madctl = model.get_sequence(config)
260
+ cg.add(var.set_init_sequence(sequence))
261
+ cg.add(var.set_madctl(madctl))
262
+
263
+ cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
264
+ cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
265
+ cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
266
+ cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH]))
267
+ cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH]))
268
+ cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
269
+ cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
270
+ cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
271
+ cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
272
+ cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
273
+ index = 0
274
+ dpins = []
275
+ if CONF_RED in config[CONF_DATA_PINS]:
276
+ red_pins = config[CONF_DATA_PINS][CONF_RED]
277
+ green_pins = config[CONF_DATA_PINS][CONF_GREEN]
278
+ blue_pins = config[CONF_DATA_PINS][CONF_BLUE]
279
+ if config[CONF_COLOR_ORDER] == "BGR":
280
+ dpins.extend(red_pins)
281
+ dpins.extend(green_pins)
282
+ dpins.extend(blue_pins)
283
+ else:
284
+ dpins.extend(blue_pins)
285
+ dpins.extend(green_pins)
286
+ dpins.extend(red_pins)
287
+ # swap bytes to match big-endian format
288
+ dpins = dpins[8:16] + dpins[0:8]
289
+ else:
290
+ dpins = config[CONF_DATA_PINS]
291
+ for index, pin in enumerate(dpins):
292
+ data_pin = await cg.gpio_pin_expression(pin)
293
+ cg.add(var.add_data_pin(data_pin, index))
294
+
295
+ if dc_pin := config.get(CONF_DC_PIN):
296
+ dc = await cg.gpio_pin_expression(dc_pin)
297
+ cg.add(var.set_dc_pin(dc))
298
+
299
+ if reset_pin := config.get(CONF_RESET_PIN):
300
+ reset = await cg.gpio_pin_expression(reset_pin)
301
+ cg.add(var.set_reset_pin(reset))
302
+
303
+ if model.rotation_as_transform(config):
304
+ config[CONF_ROTATION] = 0
305
+
306
+ if de_pin := config.get(CONF_DE_PIN):
307
+ pin = await cg.gpio_pin_expression(de_pin)
308
+ cg.add(var.set_de_pin(pin))
309
+ pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN])
310
+ cg.add(var.set_pclk_pin(pin))
311
+ pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN])
312
+ cg.add(var.set_hsync_pin(pin))
313
+ pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN])
314
+ cg.add(var.set_vsync_pin(pin))
315
+
316
+ await display.register_display(var, config)
317
+ if lamb := config.get(CONF_LAMBDA):
318
+ lambda_ = await cg.process_lambda(
319
+ lamb, [(display.DisplayRef, "it")], return_type=cg.void
320
+ )
321
+ cg.add(var.set_writer(lambda_))
@@ -0,0 +1,388 @@
1
+ #ifdef USE_ESP32_VARIANT_ESP32S3
2
+ #include "mipi_rgb.h"
3
+ #include "esphome/core/log.h"
4
+ #include "esphome/core/hal.h"
5
+ #include "esp_lcd_panel_rgb.h"
6
+
7
+ namespace esphome {
8
+ namespace mipi_rgb {
9
+
10
+ static const uint8_t DELAY_FLAG = 0xFF;
11
+ static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
12
+ static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
13
+ static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
14
+ static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
15
+ static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
16
+ static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
17
+ static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
18
+
19
+ void MipiRgb::setup_enables_() {
20
+ if (!this->enable_pins_.empty()) {
21
+ for (auto *pin : this->enable_pins_) {
22
+ pin->setup();
23
+ pin->digital_write(true);
24
+ }
25
+ delay(10);
26
+ }
27
+ if (this->reset_pin_ != nullptr) {
28
+ this->reset_pin_->setup();
29
+ this->reset_pin_->digital_write(true);
30
+ delay(5);
31
+ this->reset_pin_->digital_write(false);
32
+ delay(5);
33
+ this->reset_pin_->digital_write(true);
34
+ }
35
+ }
36
+
37
+ #ifdef USE_SPI
38
+ void MipiRgbSpi::setup() {
39
+ this->setup_enables_();
40
+ this->spi_setup();
41
+ this->write_init_sequence_();
42
+ this->common_setup_();
43
+ }
44
+ void MipiRgbSpi::write_command_(uint8_t value) {
45
+ this->enable();
46
+ if (this->dc_pin_ == nullptr) {
47
+ this->write(value, 9);
48
+ } else {
49
+ this->dc_pin_->digital_write(false);
50
+ this->write_byte(value);
51
+ this->dc_pin_->digital_write(true);
52
+ }
53
+ this->disable();
54
+ }
55
+
56
+ void MipiRgbSpi::write_data_(uint8_t value) {
57
+ this->enable();
58
+ if (this->dc_pin_ == nullptr) {
59
+ this->write(value | 0x100, 9);
60
+ } else {
61
+ this->dc_pin_->digital_write(true);
62
+ this->write_byte(value);
63
+ }
64
+ this->disable();
65
+ }
66
+
67
+ /**
68
+ * this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
69
+ */
70
+
71
+ void MipiRgbSpi::write_init_sequence_() {
72
+ size_t index = 0;
73
+ auto &vec = this->init_sequence_;
74
+ while (index != vec.size()) {
75
+ if (vec.size() - index < 2) {
76
+ this->mark_failed("Malformed init sequence");
77
+ return;
78
+ }
79
+ uint8_t cmd = vec[index++];
80
+ uint8_t x = vec[index++];
81
+ if (x == DELAY_FLAG) {
82
+ ESP_LOGD(TAG, "Delay %dms", cmd);
83
+ delay(cmd);
84
+ } else {
85
+ uint8_t num_args = x & 0x7F;
86
+ if (vec.size() - index < num_args) {
87
+ this->mark_failed("Malformed init sequence");
88
+ return;
89
+ }
90
+ if (cmd == SLEEP_OUT) {
91
+ delay(120); // NOLINT
92
+ }
93
+ const auto *ptr = vec.data() + index;
94
+ ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
95
+ format_hex_pretty(ptr, num_args, '.', false).c_str());
96
+ index += num_args;
97
+ this->write_command_(cmd);
98
+ while (num_args-- != 0)
99
+ this->write_data_(*ptr++);
100
+ if (cmd == SLEEP_OUT)
101
+ delay(10);
102
+ }
103
+ }
104
+ // this->spi_teardown(); // SPI not needed after this
105
+ this->init_sequence_.clear();
106
+ delay(10);
107
+ }
108
+
109
+ void MipiRgbSpi::dump_config() {
110
+ MipiRgb::dump_config();
111
+ LOG_PIN(" CS Pin: ", this->cs_);
112
+ LOG_PIN(" DC Pin: ", this->dc_pin_);
113
+ ESP_LOGCONFIG(TAG,
114
+ " SPI Data rate: %uMHz"
115
+ "\n Mirror X: %s"
116
+ "\n Mirror Y: %s"
117
+ "\n Swap X/Y: %s"
118
+ "\n Color Order: %s",
119
+ (unsigned) (this->data_rate_ / 1000000), YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
120
+ YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY | MADCTL_ML)), YESNO(this->madctl_ & MADCTL_MV),
121
+ this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
122
+ }
123
+
124
+ #endif // USE_SPI
125
+
126
+ void MipiRgb::setup() {
127
+ this->setup_enables_();
128
+ this->common_setup_();
129
+ }
130
+
131
+ void MipiRgb::common_setup_() {
132
+ esp_lcd_rgb_panel_config_t config{};
133
+ config.flags.fb_in_psram = 1;
134
+ config.bounce_buffer_size_px = this->width_ * 10;
135
+ config.num_fbs = 1;
136
+ config.timings.h_res = this->width_;
137
+ config.timings.v_res = this->height_;
138
+ config.timings.hsync_pulse_width = this->hsync_pulse_width_;
139
+ config.timings.hsync_back_porch = this->hsync_back_porch_;
140
+ config.timings.hsync_front_porch = this->hsync_front_porch_;
141
+ config.timings.vsync_pulse_width = this->vsync_pulse_width_;
142
+ config.timings.vsync_back_porch = this->vsync_back_porch_;
143
+ config.timings.vsync_front_porch = this->vsync_front_porch_;
144
+ config.timings.flags.pclk_active_neg = this->pclk_inverted_;
145
+ config.timings.pclk_hz = this->pclk_frequency_;
146
+ config.clk_src = LCD_CLK_SRC_PLL160M;
147
+ size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
148
+ for (size_t i = 0; i != data_pin_count; i++) {
149
+ config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
150
+ }
151
+ config.data_width = data_pin_count;
152
+ config.disp_gpio_num = -1;
153
+ config.hsync_gpio_num = this->hsync_pin_->get_pin();
154
+ config.vsync_gpio_num = this->vsync_pin_->get_pin();
155
+ if (this->de_pin_) {
156
+ config.de_gpio_num = this->de_pin_->get_pin();
157
+ } else {
158
+ config.de_gpio_num = -1;
159
+ }
160
+ config.pclk_gpio_num = this->pclk_pin_->get_pin();
161
+ esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
162
+ if (err == ESP_OK)
163
+ err = esp_lcd_panel_reset(this->handle_);
164
+ if (err == ESP_OK)
165
+ err = esp_lcd_panel_init(this->handle_);
166
+ if (err != ESP_OK) {
167
+ auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
168
+ this->mark_failed(msg.c_str());
169
+ }
170
+ ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
171
+ }
172
+
173
+ void MipiRgb::loop() {
174
+ if (this->handle_ != nullptr)
175
+ esp_lcd_rgb_panel_restart(this->handle_);
176
+ }
177
+
178
+ void MipiRgb::update() {
179
+ if (this->is_failed())
180
+ return;
181
+ if (this->auto_clear_enabled_) {
182
+ this->clear();
183
+ }
184
+ if (this->show_test_card_) {
185
+ this->test_card();
186
+ } else if (this->page_ != nullptr) {
187
+ this->page_->get_writer()(*this);
188
+ } else if (this->writer_.has_value()) {
189
+ (*this->writer_)(*this);
190
+ } else {
191
+ this->stop_poller();
192
+ }
193
+ if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
194
+ return;
195
+ ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_);
196
+ int w = this->x_high_ - this->x_low_ + 1;
197
+ int h = this->y_high_ - this->y_low_ + 1;
198
+ this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
199
+ this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
200
+ // invalidate watermarks
201
+ this->x_low_ = this->width_;
202
+ this->y_low_ = this->height_;
203
+ this->x_high_ = 0;
204
+ this->y_high_ = 0;
205
+ }
206
+
207
+ void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
208
+ display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
209
+ if (w <= 0 || h <= 0 || this->is_failed())
210
+ return;
211
+ // if color mapping is required, pass the buck.
212
+ // note that endianness is not considered here - it is assumed to match!
213
+ if (bitness != display::COLOR_BITNESS_565) {
214
+ Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
215
+ this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
216
+ this->width_ - w - x_start);
217
+ } else {
218
+ this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
219
+ }
220
+ }
221
+
222
+ void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
223
+ int x_pad) {
224
+ esp_err_t err = ESP_OK;
225
+ auto stride = (x_offset + w + x_pad) * 2;
226
+ ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
227
+ // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
228
+ if (x_offset == 0 && x_pad == 0) {
229
+ err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
230
+ } else {
231
+ // draw line by line
232
+ for (int y = 0; y != h; y++) {
233
+ err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
234
+ if (err != ESP_OK)
235
+ break;
236
+ ptr += stride; // next line
237
+ }
238
+ }
239
+ if (err != ESP_OK)
240
+ ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
241
+ }
242
+
243
+ bool MipiRgb::check_buffer_() {
244
+ if (this->is_failed())
245
+ return false;
246
+ if (this->buffer_ != nullptr)
247
+ return true;
248
+ // this is dependent on the enum values.
249
+ RAMAllocator<uint16_t> allocator;
250
+ this->buffer_ = allocator.allocate(this->height_ * this->width_);
251
+ if (this->buffer_ == nullptr) {
252
+ this->mark_failed("Could not allocate buffer for display!");
253
+ return false;
254
+ }
255
+ return true;
256
+ }
257
+
258
+ void MipiRgb::draw_pixel_at(int x, int y, Color color) {
259
+ if (!this->get_clipping().inside(x, y) || this->is_failed())
260
+ return;
261
+
262
+ switch (this->rotation_) {
263
+ case display::DISPLAY_ROTATION_0_DEGREES:
264
+ break;
265
+ case display::DISPLAY_ROTATION_90_DEGREES:
266
+ std::swap(x, y);
267
+ x = this->width_ - x - 1;
268
+ break;
269
+ case display::DISPLAY_ROTATION_180_DEGREES:
270
+ x = this->width_ - x - 1;
271
+ y = this->height_ - y - 1;
272
+ break;
273
+ case display::DISPLAY_ROTATION_270_DEGREES:
274
+ std::swap(x, y);
275
+ y = this->height_ - y - 1;
276
+ break;
277
+ }
278
+ if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
279
+ return;
280
+ }
281
+ if (!this->check_buffer_())
282
+ return;
283
+ size_t pos = (y * this->width_) + x;
284
+ uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
285
+ uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
286
+ uint16_t new_color = hi_byte | (lo_byte << 8); // big endian
287
+ if (this->buffer_[pos] == new_color)
288
+ return;
289
+ this->buffer_[pos] = new_color;
290
+ // low and high watermark may speed up drawing from buffer
291
+ if (x < this->x_low_)
292
+ this->x_low_ = x;
293
+ if (y < this->y_low_)
294
+ this->y_low_ = y;
295
+ if (x > this->x_high_)
296
+ this->x_high_ = x;
297
+ if (y > this->y_high_)
298
+ this->y_high_ = y;
299
+ }
300
+ void MipiRgb::fill(Color color) {
301
+ if (!this->check_buffer_())
302
+ return;
303
+ auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
304
+ uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
305
+ uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
306
+ uint16_t new_color = lo_byte | (hi_byte << 8); // little endian
307
+ std::fill_n(ptr_16, this->width_ * this->height_, new_color);
308
+ }
309
+
310
+ int MipiRgb::get_width() {
311
+ switch (this->rotation_) {
312
+ case display::DISPLAY_ROTATION_90_DEGREES:
313
+ case display::DISPLAY_ROTATION_270_DEGREES:
314
+ return this->get_height_internal();
315
+ case display::DISPLAY_ROTATION_0_DEGREES:
316
+ case display::DISPLAY_ROTATION_180_DEGREES:
317
+ default:
318
+ return this->get_width_internal();
319
+ }
320
+ }
321
+
322
+ int MipiRgb::get_height() {
323
+ switch (this->rotation_) {
324
+ case display::DISPLAY_ROTATION_0_DEGREES:
325
+ case display::DISPLAY_ROTATION_180_DEGREES:
326
+ return this->get_height_internal();
327
+ case display::DISPLAY_ROTATION_90_DEGREES:
328
+ case display::DISPLAY_ROTATION_270_DEGREES:
329
+ default:
330
+ return this->get_width_internal();
331
+ }
332
+ }
333
+
334
+ static std::string get_pin_name(GPIOPin *pin) {
335
+ if (pin == nullptr)
336
+ return "None";
337
+ return pin->dump_summary();
338
+ }
339
+
340
+ void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
341
+ for (uint8_t i = start; i != end; i++) {
342
+ ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, this->data_pins_[i]->dump_summary().c_str());
343
+ }
344
+ }
345
+
346
+ void MipiRgb::dump_config() {
347
+ ESP_LOGCONFIG(TAG,
348
+ "MIPI_RGB LCD"
349
+ "\n Model: %s"
350
+ "\n Width: %u"
351
+ "\n Height: %u"
352
+ "\n Rotation: %d degrees"
353
+ "\n HSync Pulse Width: %u"
354
+ "\n HSync Back Porch: %u"
355
+ "\n HSync Front Porch: %u"
356
+ "\n VSync Pulse Width: %u"
357
+ "\n VSync Back Porch: %u"
358
+ "\n VSync Front Porch: %u"
359
+ "\n Invert Colors: %s"
360
+ "\n Pixel Clock: %dMHz"
361
+ "\n Reset Pin: %s"
362
+ "\n DE Pin: %s"
363
+ "\n PCLK Pin: %s"
364
+ "\n HSYNC Pin: %s"
365
+ "\n VSYNC Pin: %s",
366
+ this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
367
+ this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_,
368
+ this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
369
+ get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
370
+ get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
371
+ get_pin_name(this->vsync_pin_).c_str());
372
+
373
+ if (this->madctl_ & MADCTL_BGR) {
374
+ this->dump_pins_(8, 13, "Blue", 0);
375
+ this->dump_pins_(13, 16, "Green", 0);
376
+ this->dump_pins_(0, 3, "Green", 3);
377
+ this->dump_pins_(3, 8, "Red", 0);
378
+ } else {
379
+ this->dump_pins_(8, 13, "Red", 0);
380
+ this->dump_pins_(13, 16, "Green", 0);
381
+ this->dump_pins_(0, 3, "Green", 3);
382
+ this->dump_pins_(3, 8, "Blue", 0);
383
+ }
384
+ }
385
+
386
+ } // namespace mipi_rgb
387
+ } // namespace esphome
388
+ #endif // USE_ESP32_VARIANT_ESP32S3