esphome 2024.12.3__py3-none-any.whl → 2025.2.0__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 (366) hide show
  1. esphome/__main__.py +16 -3
  2. esphome/components/adc/__init__.py +17 -11
  3. esphome/components/adc/adc_sensor.h +17 -0
  4. esphome/components/adc/adc_sensor_common.cpp +55 -0
  5. esphome/components/adc/adc_sensor_esp32.cpp +8 -5
  6. esphome/components/adc/adc_sensor_esp8266.cpp +10 -6
  7. esphome/components/adc/adc_sensor_libretiny.cpp +11 -6
  8. esphome/components/adc/adc_sensor_rp2040.cpp +13 -10
  9. esphome/components/adc/sensor.py +9 -3
  10. esphome/components/ads1115/ads1115.cpp +56 -7
  11. esphome/components/ads1115/ads1115.h +13 -1
  12. esphome/components/ads1115/sensor/__init__.py +16 -0
  13. esphome/components/ads1115/sensor/ads1115_sensor.cpp +2 -1
  14. esphome/components/ads1115/sensor/ads1115_sensor.h +2 -0
  15. esphome/components/animation/__init__.py +23 -261
  16. esphome/components/animation/animation.cpp +2 -2
  17. esphome/components/animation/animation.h +2 -1
  18. esphome/components/api/api_pb2.cpp +14 -0
  19. esphome/components/api/api_pb2.h +1 -0
  20. esphome/components/api/client.py +8 -3
  21. esphome/components/audio/__init__.py +112 -0
  22. esphome/components/audio/audio.cpp +67 -0
  23. esphome/components/audio/audio.h +125 -7
  24. esphome/components/audio/audio_decoder.cpp +361 -0
  25. esphome/components/audio/audio_decoder.h +135 -0
  26. esphome/components/audio/audio_reader.cpp +308 -0
  27. esphome/components/audio/audio_reader.h +85 -0
  28. esphome/components/audio/audio_resampler.cpp +159 -0
  29. esphome/components/audio/audio_resampler.h +101 -0
  30. esphome/components/audio/audio_transfer_buffer.cpp +165 -0
  31. esphome/components/audio/audio_transfer_buffer.h +139 -0
  32. esphome/components/audio_adc/__init__.py +41 -0
  33. esphome/components/audio_adc/audio_adc.h +17 -0
  34. esphome/components/audio_adc/automation.h +23 -0
  35. esphome/components/bk72xx/__init__.py +1 -0
  36. esphome/components/ble_client/ble_client.cpp +1 -2
  37. esphome/components/ble_client/sensor/__init__.py +1 -1
  38. esphome/components/ble_client/text_sensor/__init__.py +1 -1
  39. esphome/components/bluetooth_proxy/bluetooth_connection.cpp +5 -0
  40. esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -0
  41. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +5 -0
  42. esphome/components/ch422g/ch422g.h +2 -0
  43. esphome/components/climate/__init__.py +1 -1
  44. esphome/components/climate_ir/climate_ir.cpp +2 -1
  45. esphome/components/coolix/coolix.cpp +2 -1
  46. esphome/components/cse7766/cse7766.cpp +8 -16
  47. esphome/components/custom/__init__.py +0 -3
  48. esphome/components/custom/binary_sensor/__init__.py +2 -28
  49. esphome/components/custom/climate/__init__.py +2 -27
  50. esphome/components/custom/cover/__init__.py +2 -27
  51. esphome/components/custom/light/__init__.py +2 -27
  52. esphome/components/custom/output/__init__.py +2 -58
  53. esphome/components/custom/sensor/__init__.py +2 -24
  54. esphome/components/custom/switch/__init__.py +2 -24
  55. esphome/components/custom/text_sensor/__init__.py +2 -29
  56. esphome/components/custom_component/__init__.py +3 -27
  57. esphome/components/daly_bms/daly_bms.cpp +6 -0
  58. esphome/components/daly_bms/daly_bms.h +2 -0
  59. esphome/components/daly_bms/sensor.py +6 -0
  60. esphome/components/debug/debug_component.cpp +4 -0
  61. esphome/components/debug/debug_component.h +14 -0
  62. esphome/components/debug/debug_esp32.cpp +154 -74
  63. esphome/components/dfplayer/dfplayer.cpp +15 -2
  64. esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp +2 -1
  65. esphome/components/dht/dht.cpp +4 -2
  66. esphome/components/dht/sensor.py +1 -1
  67. esphome/components/display/__init__.py +18 -5
  68. esphome/components/display/display.cpp +16 -3
  69. esphome/components/display/rect.cpp +2 -1
  70. esphome/components/es7210/__init__.py +0 -0
  71. esphome/components/es7210/audio_adc.py +51 -0
  72. esphome/components/es7210/es7210.cpp +228 -0
  73. esphome/components/es7210/es7210.h +62 -0
  74. esphome/components/es7210/es7210_const.h +129 -0
  75. esphome/components/es7243e/__init__.py +0 -0
  76. esphome/components/es7243e/audio_adc.py +34 -0
  77. esphome/components/es7243e/es7243e.cpp +125 -0
  78. esphome/components/es7243e/es7243e.h +37 -0
  79. esphome/components/es7243e/es7243e_const.h +54 -0
  80. esphome/components/es8156/__init__.py +0 -0
  81. esphome/components/es8156/audio_dac.py +27 -0
  82. esphome/components/es8156/es8156.cpp +87 -0
  83. esphome/components/es8156/es8156.h +51 -0
  84. esphome/components/es8156/es8156_const.h +68 -0
  85. esphome/components/es8311/audio_dac.py +1 -2
  86. esphome/components/esp32/__init__.py +1 -0
  87. esphome/components/esp32/core.cpp +5 -1
  88. esphome/components/esp32/gpio.h +2 -0
  89. esphome/components/esp32_ble/__init__.py +39 -0
  90. esphome/components/esp32_ble/queue.h +4 -4
  91. esphome/components/esp32_ble_client/ble_client_base.cpp +46 -0
  92. esphome/components/esp32_ble_client/ble_client_base.h +2 -0
  93. esphome/components/esp32_ble_server/__init__.py +582 -12
  94. esphome/components/esp32_ble_server/ble_characteristic.cpp +48 -60
  95. esphome/components/esp32_ble_server/ble_characteristic.h +24 -17
  96. esphome/components/esp32_ble_server/ble_descriptor.cpp +21 -9
  97. esphome/components/esp32_ble_server/ble_descriptor.h +17 -6
  98. esphome/components/esp32_ble_server/ble_server.cpp +62 -67
  99. esphome/components/esp32_ble_server/ble_server.h +28 -32
  100. esphome/components/esp32_ble_server/ble_server_automations.cpp +77 -0
  101. esphome/components/esp32_ble_server/ble_server_automations.h +115 -0
  102. esphome/components/esp32_ble_server/ble_service.cpp +17 -15
  103. esphome/components/esp32_ble_server/ble_service.h +10 -14
  104. esphome/components/esp32_ble_tracker/__init__.py +6 -39
  105. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +33 -10
  106. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +8 -4
  107. esphome/components/esp32_dac/esp32_dac.cpp +16 -7
  108. esphome/components/esp32_dac/esp32_dac.h +8 -0
  109. esphome/components/esp32_dac/output.py +16 -4
  110. esphome/components/esp32_improv/__init__.py +2 -8
  111. esphome/components/esp32_improv/esp32_improv_component.cpp +21 -20
  112. esphome/components/esp32_improv/esp32_improv_component.h +3 -4
  113. esphome/components/esp32_rmt/__init__.py +28 -3
  114. esphome/components/esp32_rmt_led_strip/led_strip.cpp +73 -6
  115. esphome/components/esp32_rmt_led_strip/led_strip.h +21 -3
  116. esphome/components/esp32_rmt_led_strip/light.py +72 -7
  117. esphome/components/esp32_touch/esp32_touch.cpp +5 -0
  118. esphome/components/esp8266/__init__.py +1 -0
  119. esphome/components/esp8266/gpio.h +1 -0
  120. esphome/components/ethernet/__init__.py +10 -10
  121. esphome/components/event/event.cpp +4 -2
  122. esphome/components/event/event.h +2 -0
  123. esphome/components/event_emitter/__init__.py +5 -0
  124. esphome/components/event_emitter/event_emitter.cpp +14 -0
  125. esphome/components/event_emitter/event_emitter.h +63 -0
  126. esphome/components/font/__init__.py +1 -1
  127. esphome/components/gcja5/gcja5.cpp +2 -1
  128. esphome/components/graph/graph.cpp +4 -9
  129. esphome/components/haier/haier_base.cpp +2 -1
  130. esphome/components/haier/hon_climate.cpp +2 -1
  131. esphome/components/heatpumpir/heatpumpir.cpp +2 -1
  132. esphome/components/host/__init__.py +1 -0
  133. esphome/components/host/gpio.h +1 -0
  134. esphome/components/http_request/http_request.h +2 -2
  135. esphome/components/http_request/http_request_arduino.cpp +1 -1
  136. esphome/components/http_request/http_request_idf.cpp +1 -1
  137. esphome/components/i2c/i2c_bus_esp_idf.cpp +4 -0
  138. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +7 -5
  139. esphome/components/i2s_audio/speaker/__init__.py +53 -6
  140. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +92 -46
  141. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +8 -0
  142. esphome/components/ili9xxx/display.py +29 -11
  143. esphome/components/ili9xxx/ili9xxx_display.cpp +2 -5
  144. esphome/components/ili9xxx/ili9xxx_display.h +2 -1
  145. esphome/components/image/__init__.py +443 -255
  146. esphome/components/image/image.cpp +115 -61
  147. esphome/components/image/image.h +15 -24
  148. esphome/components/json/json_util.cpp +8 -34
  149. esphome/components/libretiny/__init__.py +1 -0
  150. esphome/components/libretiny/gpio_arduino.h +1 -0
  151. esphome/components/light/light_color_values.h +1 -1
  152. esphome/components/logger/__init__.py +45 -9
  153. esphome/components/logger/logger.cpp +16 -14
  154. esphome/components/logger/logger.h +11 -7
  155. esphome/components/logger/select/__init__.py +29 -0
  156. esphome/components/logger/select/logger_level_select.cpp +27 -0
  157. esphome/components/logger/select/logger_level_select.h +15 -0
  158. esphome/components/lvgl/__init__.py +96 -73
  159. esphome/components/lvgl/automation.py +39 -7
  160. esphome/components/lvgl/defines.py +8 -2
  161. esphome/components/lvgl/lvgl_esphome.cpp +8 -15
  162. esphome/components/lvgl/lvgl_esphome.h +20 -5
  163. esphome/components/lvgl/schemas.py +25 -14
  164. esphome/components/lvgl/trigger.py +27 -3
  165. esphome/components/lvgl/widgets/dropdown.py +1 -1
  166. esphome/components/lvgl/widgets/keyboard.py +8 -1
  167. esphome/components/lvgl/widgets/meter.py +2 -1
  168. esphome/components/lvgl/widgets/msgbox.py +1 -1
  169. esphome/components/lvgl/widgets/obj.py +1 -12
  170. esphome/components/lvgl/widgets/page.py +37 -2
  171. esphome/components/lvgl/widgets/tabview.py +1 -1
  172. esphome/components/max6956/max6956.h +2 -0
  173. esphome/components/mcp23016/mcp23016.h +2 -0
  174. esphome/components/mcp23xxx_base/mcp23xxx_base.h +2 -0
  175. esphome/components/mdns/__init__.py +1 -1
  176. esphome/components/media_player/__init__.py +37 -8
  177. esphome/components/media_player/automation.h +11 -2
  178. esphome/components/media_player/media_player.cpp +8 -0
  179. esphome/components/media_player/media_player.h +8 -4
  180. esphome/components/micronova/switch/micronova_switch.cpp +4 -2
  181. esphome/components/midea/ac_automations.h +3 -1
  182. esphome/components/midea/air_conditioner.cpp +7 -5
  183. esphome/components/midea/air_conditioner.h +1 -1
  184. esphome/components/midea/climate.py +4 -2
  185. esphome/components/midea/ir_transmitter.h +36 -5
  186. esphome/components/mixer/__init__.py +0 -0
  187. esphome/components/mixer/speaker/__init__.py +172 -0
  188. esphome/components/mixer/speaker/automation.h +19 -0
  189. esphome/components/mixer/speaker/mixer_speaker.cpp +624 -0
  190. esphome/components/mixer/speaker/mixer_speaker.h +207 -0
  191. esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +7 -13
  192. esphome/components/mpr121/mpr121.h +2 -0
  193. esphome/components/mqtt/__init__.py +1 -1
  194. esphome/components/mqtt/mqtt_client.cpp +7 -1
  195. esphome/components/mqtt/mqtt_client.h +1 -1
  196. esphome/components/mqtt/mqtt_climate.cpp +2 -2
  197. esphome/components/network/ip_address.h +2 -0
  198. esphome/components/nextion/automation.h +17 -0
  199. esphome/components/nextion/display.py +42 -17
  200. esphome/components/nextion/nextion.cpp +4 -10
  201. esphome/components/nextion/nextion.h +89 -82
  202. esphome/components/nextion/nextion_commands.cpp +10 -10
  203. esphome/components/ntc/sensor.py +2 -4
  204. esphome/components/online_image/__init__.py +98 -46
  205. esphome/components/online_image/bmp_image.cpp +101 -0
  206. esphome/components/online_image/bmp_image.h +40 -0
  207. esphome/components/online_image/image_decoder.cpp +31 -2
  208. esphome/components/online_image/image_decoder.h +24 -15
  209. esphome/components/online_image/jpeg_image.cpp +92 -0
  210. esphome/components/online_image/jpeg_image.h +34 -0
  211. esphome/components/online_image/online_image.cpp +118 -58
  212. esphome/components/online_image/online_image.h +39 -9
  213. esphome/components/online_image/png_image.cpp +7 -3
  214. esphome/components/online_image/png_image.h +2 -1
  215. esphome/components/opentherm/__init__.py +73 -7
  216. esphome/components/opentherm/automation.h +25 -0
  217. esphome/components/opentherm/const.py +1 -0
  218. esphome/components/opentherm/generate.py +39 -6
  219. esphome/components/opentherm/hub.cpp +117 -79
  220. esphome/components/opentherm/hub.h +31 -15
  221. esphome/components/opentherm/opentherm.cpp +47 -23
  222. esphome/components/opentherm/opentherm.h +27 -6
  223. esphome/components/opentherm/opentherm_macros.h +11 -0
  224. esphome/components/opentherm/schema.py +78 -1
  225. esphome/components/opentherm/validate.py +7 -2
  226. esphome/components/pca6416a/pca6416a.h +2 -0
  227. esphome/components/pca9554/pca9554.h +2 -0
  228. esphome/components/pcf8574/pcf8574.h +2 -0
  229. esphome/components/preferences/__init__.py +2 -4
  230. esphome/components/preferences/syncer.h +10 -3
  231. esphome/components/prometheus/prometheus_handler.cpp +313 -0
  232. esphome/components/prometheus/prometheus_handler.h +48 -7
  233. esphome/components/psram/psram.cpp +8 -1
  234. esphome/components/pulse_counter/pulse_counter_sensor.cpp +14 -9
  235. esphome/components/pulse_counter/pulse_counter_sensor.h +4 -4
  236. esphome/components/pulse_meter/pulse_meter_sensor.cpp +2 -0
  237. esphome/components/qspi_dbi/__init__.py +3 -0
  238. esphome/components/qspi_dbi/display.py +74 -47
  239. esphome/components/qspi_dbi/models.py +245 -2
  240. esphome/components/qspi_dbi/qspi_dbi.cpp +9 -16
  241. esphome/components/qspi_dbi/qspi_dbi.h +2 -2
  242. esphome/components/remote_base/__init__.py +77 -25
  243. esphome/components/remote_base/remote_base.cpp +1 -1
  244. esphome/components/remote_base/remote_base.h +20 -2
  245. esphome/components/remote_base/toto_protocol.cpp +100 -0
  246. esphome/components/remote_base/toto_protocol.h +45 -0
  247. esphome/components/remote_receiver/__init__.py +55 -10
  248. esphome/components/remote_receiver/remote_receiver.h +36 -3
  249. esphome/components/remote_receiver/remote_receiver_esp32.cpp +145 -6
  250. esphome/components/remote_transmitter/__init__.py +62 -4
  251. esphome/components/remote_transmitter/remote_transmitter.h +21 -2
  252. esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +140 -4
  253. esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +3 -3
  254. esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +3 -3
  255. esphome/components/resampler/__init__.py +0 -0
  256. esphome/components/resampler/speaker/__init__.py +103 -0
  257. esphome/components/resampler/speaker/resampler_speaker.cpp +318 -0
  258. esphome/components/resampler/speaker/resampler_speaker.h +107 -0
  259. esphome/components/resistance/resistance_sensor.h +2 -3
  260. esphome/components/resistance/sensor.py +2 -9
  261. esphome/components/rotary_encoder/rotary_encoder.cpp +8 -4
  262. esphome/components/rp2040/__init__.py +1 -0
  263. esphome/components/rp2040/gpio.h +1 -0
  264. esphome/components/rtl87xx/__init__.py +2 -0
  265. esphome/components/scd30/sensor.py +1 -1
  266. esphome/components/sdl/binary_sensor.py +270 -0
  267. esphome/components/sdl/sdl_esphome.cpp +16 -0
  268. esphome/components/sdl/sdl_esphome.h +9 -0
  269. esphome/components/seeed_mr60bha2/binary_sensor.py +25 -0
  270. esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +26 -2
  271. esphome/components/seeed_mr60bha2/seeed_mr60bha2.h +9 -20
  272. esphome/components/seeed_mr60bha2/sensor.py +9 -1
  273. esphome/components/sn74hc165/sn74hc165.h +3 -0
  274. esphome/components/sn74hc595/sn74hc595.h +3 -0
  275. esphome/components/speaker/__init__.py +5 -4
  276. esphome/components/speaker/media_player/__init__.py +458 -0
  277. esphome/components/speaker/media_player/audio_pipeline.cpp +568 -0
  278. esphome/components/speaker/media_player/audio_pipeline.h +159 -0
  279. esphome/components/speaker/media_player/automation.h +26 -0
  280. esphome/components/speaker/media_player/speaker_media_player.cpp +577 -0
  281. esphome/components/speaker/media_player/speaker_media_player.h +160 -0
  282. esphome/components/speaker/speaker.h +20 -0
  283. esphome/components/spi/__init__.py +1 -5
  284. esphome/components/spi/spi.cpp +7 -1
  285. esphome/components/spi/spi.h +21 -2
  286. esphome/components/spi_led_strip/light.py +3 -5
  287. esphome/components/spi_led_strip/spi_led_strip.cpp +67 -0
  288. esphome/components/spi_led_strip/spi_led_strip.h +8 -60
  289. esphome/components/sprinkler/sprinkler.cpp +3 -1
  290. esphome/components/sx1509/sx1509_gpio_pin.h +2 -0
  291. esphome/components/tca9555/tca9555.h +2 -0
  292. esphome/components/toshiba/toshiba.cpp +2 -1
  293. esphome/components/tuya/light/tuya_light.cpp +4 -2
  294. esphome/components/uart/uart_component_esp32_arduino.cpp +2 -2
  295. esphome/components/uart/uart_component_esp_idf.cpp +2 -2
  296. esphome/components/udp/__init__.py +8 -2
  297. esphome/components/udp/udp_component.cpp +25 -56
  298. esphome/components/udp/udp_component.h +3 -0
  299. esphome/components/uponor_smatrix/sensor/__init__.py +14 -4
  300. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +5 -0
  301. esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h +1 -0
  302. esphome/components/uptime/text_sensor/__init__.py +19 -0
  303. esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +63 -0
  304. esphome/components/uptime/text_sensor/uptime_text_sensor.h +25 -0
  305. esphome/components/voice_assistant/voice_assistant.cpp +24 -14
  306. esphome/components/voice_assistant/voice_assistant.h +8 -0
  307. esphome/components/waveshare_epaper/display.py +22 -1
  308. esphome/components/waveshare_epaper/waveshare_213v3.cpp +9 -3
  309. esphome/components/waveshare_epaper/waveshare_epaper.cpp +1155 -44
  310. esphome/components/waveshare_epaper/waveshare_epaper.h +208 -7
  311. esphome/components/web_server/web_server.cpp +28 -6
  312. esphome/components/weikai/weikai.h +2 -0
  313. esphome/components/wifi/__init__.py +6 -6
  314. esphome/components/wifi/wifi_component.cpp +1 -1
  315. esphome/components/wifi/wifi_component_esp32_arduino.cpp +30 -1
  316. esphome/components/wireguard/__init__.py +2 -2
  317. esphome/components/xl9535/xl9535.h +2 -0
  318. esphome/components/xxtea/__init__.py +3 -0
  319. esphome/components/xxtea/xxtea.cpp +46 -0
  320. esphome/components/xxtea/xxtea.h +26 -0
  321. esphome/components/yashima/yashima.cpp +2 -1
  322. esphome/config.py +9 -5
  323. esphome/config_validation.py +55 -17
  324. esphome/const.py +7 -10
  325. esphome/core/__init__.py +6 -13
  326. esphome/core/base_automation.h +1 -0
  327. esphome/core/config.py +59 -72
  328. esphome/core/defines.h +9 -1
  329. esphome/core/gpio.h +7 -0
  330. esphome/core/helpers.cpp +19 -15
  331. esphome/core/helpers.h +57 -8
  332. esphome/core/log.h +9 -7
  333. esphome/cpp_generator.py +2 -2
  334. esphome/dashboard/web_server.py +1 -1
  335. esphome/espota2.py +3 -2
  336. esphome/loader.py +12 -4
  337. esphome/log.py +5 -7
  338. esphome/yaml_util.py +2 -2
  339. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/METADATA +14 -9
  340. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/RECORD +349 -300
  341. esphome/components/custom/binary_sensor/custom_binary_sensor.cpp +0 -16
  342. esphome/components/custom/binary_sensor/custom_binary_sensor.h +0 -26
  343. esphome/components/custom/climate/custom_climate.h +0 -22
  344. esphome/components/custom/cover/custom_cover.h +0 -21
  345. esphome/components/custom/light/custom_light_output.h +0 -24
  346. esphome/components/custom/output/custom_output.h +0 -37
  347. esphome/components/custom/sensor/custom_sensor.cpp +0 -16
  348. esphome/components/custom/sensor/custom_sensor.h +0 -24
  349. esphome/components/custom/switch/custom_switch.cpp +0 -16
  350. esphome/components/custom/switch/custom_switch.h +0 -24
  351. esphome/components/custom/text_sensor/custom_text_sensor.cpp +0 -16
  352. esphome/components/custom/text_sensor/custom_text_sensor.h +0 -26
  353. esphome/components/custom_component/custom_component.h +0 -28
  354. esphome/components/esp32_ble_server/ble_2901.cpp +0 -18
  355. esphome/components/esp32_ble_server/ble_2901.h +0 -19
  356. esphome/components/resistance_sampler/__init__.py +0 -6
  357. esphome/components/resistance_sampler/resistance_sampler.h +0 -10
  358. esphome/components/uptime/{sensor.py → sensor/__init__.py} +3 -3
  359. /esphome/components/uptime/{uptime_seconds_sensor.cpp → sensor/uptime_seconds_sensor.cpp} +0 -0
  360. /esphome/components/uptime/{uptime_seconds_sensor.h → sensor/uptime_seconds_sensor.h} +0 -0
  361. /esphome/components/uptime/{uptime_timestamp_sensor.cpp → sensor/uptime_timestamp_sensor.cpp} +0 -0
  362. /esphome/components/uptime/{uptime_timestamp_sensor.h → sensor/uptime_timestamp_sensor.h} +0 -0
  363. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/LICENSE +0 -0
  364. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/WHEEL +0 -0
  365. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/entry_points.txt +0 -0
  366. {esphome-2024.12.3.dist-info → esphome-2025.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,577 @@
1
+ #include "speaker_media_player.h"
2
+
3
+ #ifdef USE_ESP_IDF
4
+
5
+ #include "esphome/core/log.h"
6
+
7
+ #include "esphome/components/audio/audio.h"
8
+ #ifdef USE_OTA
9
+ #include "esphome/components/ota/ota_backend.h"
10
+ #endif
11
+
12
+ namespace esphome {
13
+ namespace speaker {
14
+
15
+ // Framework:
16
+ // - Media player that can handle two streams: one for media and one for announcements
17
+ // - Each stream has an individual speaker component for output
18
+ // - Each stream is handled by an ``AudioPipeline`` object with two parts/tasks
19
+ // - ``AudioReader`` handles reading from an HTTP source or from a PROGMEM flash set at compile time
20
+ // - ``AudioDecoder`` handles decoding the audio file. All formats are limited to two channels and 16 bits per sample
21
+ // - FLAC
22
+ // - MP3 (based on the libhelix decoder)
23
+ // - WAV
24
+ // - Each task runs until it is done processing the file or it receives a stop command
25
+ // - Inter-task communication uses a FreeRTOS Event Group
26
+ // - The ``AudioPipeline`` sets up a ring buffer between the reader and decoder tasks. The decoder task outputs audio
27
+ // directly to a speaker component.
28
+ // - The pipelines internal state needs to be processed by regularly calling ``process_state``.
29
+ // - Generic media player commands are received by the ``control`` function. The commands are added to the
30
+ // ``media_control_command_queue_`` to be processed in the component's loop
31
+ // - Local file play back is initiatied with ``play_file`` and adds it to the ``media_control_command_queue_``
32
+ // - Starting a stream intializes the appropriate pipeline or stops it if it is already running
33
+ // - Volume and mute commands are achieved by the ``mute``, ``unmute``, ``set_volume`` functions.
34
+ // - Volume commands are ignored if the media control queue is full to avoid crashing with rapid volume
35
+ // increases/decreases.
36
+ // - These functions all send the appropriate information to the speakers to implement.
37
+ // - Pausing is implemented in the decoder task and is also sent directly to the media speaker component to decrease
38
+ // latency.
39
+ // - The components main loop performs housekeeping:
40
+ // - It reads the media control queue and processes it directly
41
+ // - It determines the overall state of the media player by considering the state of each pipeline
42
+ // - announcement playback takes highest priority
43
+ // - Handles playlists and repeating by starting the appropriate file when a previous file is finished
44
+ // - Logging only happens in the main loop task to reduce task stack memory usage.
45
+
46
+ static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20;
47
+
48
+ static const UBaseType_t MEDIA_PIPELINE_TASK_PRIORITY = 1;
49
+ static const UBaseType_t ANNOUNCEMENT_PIPELINE_TASK_PRIORITY = 1;
50
+
51
+ static const float FIRST_BOOT_DEFAULT_VOLUME = 0.5f;
52
+
53
+ static const char *const TAG = "speaker_media_player";
54
+
55
+ void SpeakerMediaPlayer::setup() {
56
+ state = media_player::MEDIA_PLAYER_STATE_IDLE;
57
+
58
+ this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand));
59
+
60
+ this->pref_ = global_preferences->make_preference<VolumeRestoreState>(this->get_object_id_hash());
61
+
62
+ VolumeRestoreState volume_restore_state;
63
+ if (this->pref_.load(&volume_restore_state)) {
64
+ this->set_volume_(volume_restore_state.volume);
65
+ this->set_mute_state_(volume_restore_state.is_muted);
66
+ } else {
67
+ this->set_volume_(FIRST_BOOT_DEFAULT_VOLUME);
68
+ this->set_mute_state_(false);
69
+ }
70
+
71
+ #ifdef USE_OTA
72
+ ota::get_global_ota_callback()->add_on_state_callback(
73
+ [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
74
+ if (state == ota::OTA_STARTED) {
75
+ if (this->media_pipeline_ != nullptr) {
76
+ this->media_pipeline_->suspend_tasks();
77
+ }
78
+ if (this->announcement_pipeline_ != nullptr) {
79
+ this->announcement_pipeline_->suspend_tasks();
80
+ }
81
+ } else if (state == ota::OTA_ERROR) {
82
+ if (this->media_pipeline_ != nullptr) {
83
+ this->media_pipeline_->resume_tasks();
84
+ }
85
+ if (this->announcement_pipeline_ != nullptr) {
86
+ this->announcement_pipeline_->resume_tasks();
87
+ }
88
+ }
89
+ });
90
+ #endif
91
+
92
+ this->announcement_pipeline_ =
93
+ make_unique<AudioPipeline>(this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann",
94
+ ANNOUNCEMENT_PIPELINE_TASK_PRIORITY);
95
+
96
+ if (this->announcement_pipeline_ == nullptr) {
97
+ ESP_LOGE(TAG, "Failed to create announcement pipeline");
98
+ this->mark_failed();
99
+ }
100
+
101
+ if (!this->single_pipeline_()) {
102
+ this->media_pipeline_ = make_unique<AudioPipeline>(this->media_speaker_, this->buffer_size_,
103
+ this->task_stack_in_psram_, "ann", MEDIA_PIPELINE_TASK_PRIORITY);
104
+
105
+ if (this->media_pipeline_ == nullptr) {
106
+ ESP_LOGE(TAG, "Failed to create media pipeline");
107
+ this->mark_failed();
108
+ }
109
+
110
+ // Setup callback to track the duration of audio played by the media pipeline
111
+ this->media_speaker_->add_audio_output_callback(
112
+ [this](uint32_t new_playback_ms, uint32_t remainder_us, uint32_t pending_ms, uint32_t write_timestamp) {
113
+ this->playback_ms_ += new_playback_ms;
114
+ this->remainder_us_ = remainder_us;
115
+ this->pending_ms_ = pending_ms;
116
+ this->last_audio_write_timestamp_ = write_timestamp;
117
+ this->playback_us_ = this->playback_ms_ * 1000 + this->remainder_us_;
118
+ });
119
+ }
120
+
121
+ ESP_LOGI(TAG, "Set up speaker media player");
122
+ }
123
+
124
+ void SpeakerMediaPlayer::set_playlist_delay_ms(AudioPipelineType pipeline_type, uint32_t delay_ms) {
125
+ switch (pipeline_type) {
126
+ case AudioPipelineType::ANNOUNCEMENT:
127
+ this->announcement_playlist_delay_ms_ = delay_ms;
128
+ break;
129
+ case AudioPipelineType::MEDIA:
130
+ this->media_playlist_delay_ms_ = delay_ms;
131
+ break;
132
+ }
133
+ }
134
+
135
+ void SpeakerMediaPlayer::watch_media_commands_() {
136
+ if (!this->is_ready()) {
137
+ return;
138
+ }
139
+
140
+ MediaCallCommand media_command;
141
+ esp_err_t err = ESP_OK;
142
+
143
+ if (xQueueReceive(this->media_control_command_queue_, &media_command, 0) == pdTRUE) {
144
+ bool new_url = media_command.new_url.has_value() && media_command.new_url.value();
145
+ bool new_file = media_command.new_file.has_value() && media_command.new_file.value();
146
+
147
+ if (new_url || new_file) {
148
+ bool enqueue = media_command.enqueue.has_value() && media_command.enqueue.value();
149
+
150
+ if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
151
+ // Announcement playlist/pipeline
152
+
153
+ if (!enqueue) {
154
+ // Clear the queue and ensure the loaded next item doesn't start playing
155
+ this->cancel_timeout("next_ann");
156
+ this->announcement_playlist_.clear();
157
+ }
158
+
159
+ PlaylistItem playlist_item;
160
+ if (new_url) {
161
+ playlist_item.url = this->announcement_url_;
162
+ if (!enqueue) {
163
+ // Not adding to the queue, so directly start playback and internally unpause the pipeline
164
+ this->announcement_pipeline_->start_url(playlist_item.url.value());
165
+ this->announcement_pipeline_->set_pause_state(false);
166
+ }
167
+ } else {
168
+ playlist_item.file = this->announcement_file_;
169
+ if (!enqueue) {
170
+ // Not adding to the queue, so directly start playback and internally unpause the pipeline
171
+ this->announcement_pipeline_->start_file(playlist_item.file.value());
172
+ this->announcement_pipeline_->set_pause_state(false);
173
+ }
174
+ }
175
+ this->announcement_playlist_.push_back(playlist_item);
176
+ } else {
177
+ // Media playlist/pipeline
178
+
179
+ if (!enqueue) {
180
+ // Clear the queue and ensure the loaded next item doesn't start playing
181
+ this->cancel_timeout("next_media");
182
+ this->media_playlist_.clear();
183
+ }
184
+
185
+ this->is_paused_ = false;
186
+ PlaylistItem playlist_item;
187
+ if (new_url) {
188
+ playlist_item.url = this->media_url_;
189
+ if (!enqueue) {
190
+ // Not adding to the queue, so directly start playback and internally unpause the pipeline
191
+ this->media_pipeline_->start_url(playlist_item.url.value());
192
+ this->media_pipeline_->set_pause_state(false);
193
+ }
194
+ } else {
195
+ playlist_item.file = this->media_file_;
196
+ if (!enqueue) {
197
+ // Not adding to the queue, so directly start playback and internally unpause the pipeline
198
+ this->media_pipeline_->start_file(playlist_item.file.value());
199
+ this->media_pipeline_->set_pause_state(false);
200
+ }
201
+ }
202
+ this->media_playlist_.push_back(playlist_item);
203
+ }
204
+
205
+ if (err != ESP_OK) {
206
+ ESP_LOGE(TAG, "Error starting the audio pipeline: %s", esp_err_to_name(err));
207
+ this->status_set_error();
208
+ } else {
209
+ this->status_clear_error();
210
+ }
211
+
212
+ return; // Don't process the new file play command further
213
+ }
214
+
215
+ if (media_command.volume.has_value()) {
216
+ this->set_volume_(media_command.volume.value());
217
+ this->publish_state();
218
+ }
219
+
220
+ if (media_command.command.has_value()) {
221
+ switch (media_command.command.value()) {
222
+ case media_player::MEDIA_PLAYER_COMMAND_PLAY:
223
+ if ((this->media_pipeline_ != nullptr) && (this->is_paused_)) {
224
+ this->media_pipeline_->set_pause_state(false);
225
+ }
226
+ this->is_paused_ = false;
227
+ break;
228
+ case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
229
+ if ((this->media_pipeline_ != nullptr) && (!this->is_paused_)) {
230
+ this->media_pipeline_->set_pause_state(true);
231
+ }
232
+ this->is_paused_ = true;
233
+ break;
234
+ case media_player::MEDIA_PLAYER_COMMAND_STOP:
235
+ if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
236
+ if (this->announcement_pipeline_ != nullptr) {
237
+ this->cancel_timeout("next_ann");
238
+ this->announcement_playlist_.clear();
239
+ this->announcement_pipeline_->stop();
240
+ }
241
+ } else {
242
+ if (this->media_pipeline_ != nullptr) {
243
+ this->cancel_timeout("next_media");
244
+ this->media_playlist_.clear();
245
+ this->media_pipeline_->stop();
246
+ }
247
+ }
248
+ break;
249
+ case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
250
+ if (this->media_pipeline_ != nullptr) {
251
+ if (this->is_paused_) {
252
+ this->media_pipeline_->set_pause_state(false);
253
+ this->is_paused_ = false;
254
+ } else {
255
+ this->media_pipeline_->set_pause_state(true);
256
+ this->is_paused_ = true;
257
+ }
258
+ }
259
+ break;
260
+ case media_player::MEDIA_PLAYER_COMMAND_MUTE: {
261
+ this->set_mute_state_(true);
262
+
263
+ this->publish_state();
264
+ break;
265
+ }
266
+ case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
267
+ this->set_mute_state_(false);
268
+ this->publish_state();
269
+ break;
270
+ case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP:
271
+ this->set_volume_(std::min(1.0f, this->volume + this->volume_increment_));
272
+ this->publish_state();
273
+ break;
274
+ case media_player::MEDIA_PLAYER_COMMAND_VOLUME_DOWN:
275
+ this->set_volume_(std::max(0.0f, this->volume - this->volume_increment_));
276
+ this->publish_state();
277
+ break;
278
+ case media_player::MEDIA_PLAYER_COMMAND_REPEAT_ONE:
279
+ if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
280
+ this->announcement_repeat_one_ = true;
281
+ } else {
282
+ this->media_repeat_one_ = true;
283
+ }
284
+ break;
285
+ case media_player::MEDIA_PLAYER_COMMAND_REPEAT_OFF:
286
+ if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
287
+ this->announcement_repeat_one_ = false;
288
+ } else {
289
+ this->media_repeat_one_ = false;
290
+ }
291
+ break;
292
+ case media_player::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST:
293
+ if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
294
+ if (this->announcement_playlist_.empty()) {
295
+ this->announcement_playlist_.resize(1);
296
+ }
297
+ } else {
298
+ if (this->media_playlist_.empty()) {
299
+ this->media_playlist_.resize(1);
300
+ }
301
+ }
302
+ break;
303
+ default:
304
+ break;
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ void SpeakerMediaPlayer::loop() {
311
+ this->watch_media_commands_();
312
+
313
+ // Determine state of the media player
314
+ media_player::MediaPlayerState old_state = this->state;
315
+
316
+ AudioPipelineState old_media_pipeline_state = this->media_pipeline_state_;
317
+ if (this->media_pipeline_ != nullptr) {
318
+ this->media_pipeline_state_ = this->media_pipeline_->process_state();
319
+ this->decoded_playback_ms_ = this->media_pipeline_->get_playback_ms();
320
+ }
321
+
322
+ if (this->media_pipeline_state_ == AudioPipelineState::ERROR_READING) {
323
+ ESP_LOGE(TAG, "The media pipeline's file reader encountered an error.");
324
+ } else if (this->media_pipeline_state_ == AudioPipelineState::ERROR_DECODING) {
325
+ ESP_LOGE(TAG, "The media pipeline's audio decoder encountered an error.");
326
+ }
327
+
328
+ AudioPipelineState old_announcement_pipeline_state = this->announcement_pipeline_state_;
329
+ if (this->announcement_pipeline_ != nullptr) {
330
+ this->announcement_pipeline_state_ = this->announcement_pipeline_->process_state();
331
+ }
332
+
333
+ if (this->announcement_pipeline_state_ == AudioPipelineState::ERROR_READING) {
334
+ ESP_LOGE(TAG, "The announcement pipeline's file reader encountered an error.");
335
+ } else if (this->announcement_pipeline_state_ == AudioPipelineState::ERROR_DECODING) {
336
+ ESP_LOGE(TAG, "The announcement pipeline's audio decoder encountered an error.");
337
+ }
338
+
339
+ if (this->announcement_pipeline_state_ != AudioPipelineState::STOPPED) {
340
+ this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
341
+ } else {
342
+ if (!this->announcement_playlist_.empty()) {
343
+ uint32_t timeout_ms = 0;
344
+ if (old_announcement_pipeline_state == AudioPipelineState::PLAYING) {
345
+ // Finished the current announcement file
346
+ if (!this->announcement_repeat_one_) {
347
+ // Pop item off the playlist if repeat is disabled
348
+ this->announcement_playlist_.pop_front();
349
+ }
350
+ // Only delay starting playback if moving on the next playlist item or repeating the current item
351
+ timeout_ms = this->announcement_playlist_delay_ms_;
352
+ }
353
+
354
+ if (!this->announcement_playlist_.empty()) {
355
+ // Start the next announcement file
356
+ PlaylistItem playlist_item = this->announcement_playlist_.front();
357
+ if (playlist_item.url.has_value()) {
358
+ this->announcement_pipeline_->start_url(playlist_item.url.value());
359
+ } else if (playlist_item.file.has_value()) {
360
+ this->announcement_pipeline_->start_file(playlist_item.file.value());
361
+ }
362
+
363
+ if (timeout_ms > 0) {
364
+ // Pause pipeline internally to facilitiate delay between items
365
+ this->announcement_pipeline_->set_pause_state(true);
366
+ // Internally unpause the pipeline after the delay between playlist items
367
+ this->set_timeout("next_ann", timeout_ms,
368
+ [this]() { this->announcement_pipeline_->set_pause_state(this->is_paused_); });
369
+ }
370
+ }
371
+ } else {
372
+ if (this->is_paused_) {
373
+ this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
374
+ } else if (this->media_pipeline_state_ == AudioPipelineState::PLAYING) {
375
+ this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
376
+ } else if (this->media_pipeline_state_ == AudioPipelineState::STOPPED) {
377
+ // Reset playback durations
378
+ this->decoded_playback_ms_ = 0;
379
+ this->playback_us_ = 0;
380
+ this->playback_ms_ = 0;
381
+ this->remainder_us_ = 0;
382
+ this->pending_ms_ = 0;
383
+
384
+ if (!media_playlist_.empty()) {
385
+ uint32_t timeout_ms = 0;
386
+ if (old_media_pipeline_state == AudioPipelineState::PLAYING) {
387
+ // Finished the current media file
388
+ if (!this->media_repeat_one_) {
389
+ // Pop item off the playlist if repeat is disabled
390
+ this->media_playlist_.pop_front();
391
+ }
392
+ // Only delay starting playback if moving on the next playlist item or repeating the current item
393
+ timeout_ms = this->announcement_playlist_delay_ms_;
394
+ }
395
+ if (!this->media_playlist_.empty()) {
396
+ PlaylistItem playlist_item = this->media_playlist_.front();
397
+ if (playlist_item.url.has_value()) {
398
+ this->media_pipeline_->start_url(playlist_item.url.value());
399
+ } else if (playlist_item.file.has_value()) {
400
+ this->media_pipeline_->start_file(playlist_item.file.value());
401
+ }
402
+
403
+ if (timeout_ms > 0) {
404
+ // Pause pipeline internally to facilitiate delay between items
405
+ this->media_pipeline_->set_pause_state(true);
406
+ // Internally unpause the pipeline after the delay between playlist items
407
+ this->set_timeout("next_media", timeout_ms,
408
+ [this]() { this->media_pipeline_->set_pause_state(this->is_paused_); });
409
+ }
410
+ }
411
+ } else {
412
+ this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ if (this->state != old_state) {
419
+ this->publish_state();
420
+ ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
421
+ }
422
+ }
423
+
424
+ void SpeakerMediaPlayer::play_file(audio::AudioFile *media_file, bool announcement, bool enqueue) {
425
+ if (!this->is_ready()) {
426
+ // Ignore any commands sent before the media player is setup
427
+ return;
428
+ }
429
+
430
+ MediaCallCommand media_command;
431
+
432
+ media_command.new_file = true;
433
+ if (this->single_pipeline_() || announcement) {
434
+ this->announcement_file_ = media_file;
435
+ media_command.announce = true;
436
+ } else {
437
+ this->media_file_ = media_file;
438
+ media_command.announce = false;
439
+ }
440
+ media_command.enqueue = enqueue;
441
+ xQueueSend(this->media_control_command_queue_, &media_command, portMAX_DELAY);
442
+ }
443
+
444
+ void SpeakerMediaPlayer::control(const media_player::MediaPlayerCall &call) {
445
+ if (!this->is_ready()) {
446
+ // Ignore any commands sent before the media player is setup
447
+ return;
448
+ }
449
+
450
+ MediaCallCommand media_command;
451
+
452
+ if (this->single_pipeline_() || (call.get_announcement().has_value() && call.get_announcement().value())) {
453
+ media_command.announce = true;
454
+ } else {
455
+ media_command.announce = false;
456
+ }
457
+
458
+ if (call.get_media_url().has_value()) {
459
+ std::string new_uri = call.get_media_url().value();
460
+
461
+ media_command.new_url = true;
462
+ if (this->single_pipeline_() || (call.get_announcement().has_value() && call.get_announcement().value())) {
463
+ this->announcement_url_ = new_uri;
464
+ } else {
465
+ this->media_url_ = new_uri;
466
+ }
467
+
468
+ if (call.get_command().has_value()) {
469
+ if (call.get_command().value() == media_player::MEDIA_PLAYER_COMMAND_ENQUEUE) {
470
+ media_command.enqueue = true;
471
+ }
472
+ }
473
+
474
+ xQueueSend(this->media_control_command_queue_, &media_command, portMAX_DELAY);
475
+ return;
476
+ }
477
+
478
+ if (call.get_volume().has_value()) {
479
+ media_command.volume = call.get_volume().value();
480
+ // Wait 0 ticks for queue to be free, volume sets aren't that important!
481
+ xQueueSend(this->media_control_command_queue_, &media_command, 0);
482
+ return;
483
+ }
484
+
485
+ if (call.get_command().has_value()) {
486
+ media_command.command = call.get_command().value();
487
+ TickType_t ticks_to_wait = portMAX_DELAY;
488
+ if ((call.get_command().value() == media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP) ||
489
+ (call.get_command().value() == media_player::MEDIA_PLAYER_COMMAND_VOLUME_DOWN)) {
490
+ ticks_to_wait = 0; // Wait 0 ticks for queue to be free, volume sets aren't that important!
491
+ }
492
+ xQueueSend(this->media_control_command_queue_, &media_command, ticks_to_wait);
493
+ return;
494
+ }
495
+ }
496
+
497
+ media_player::MediaPlayerTraits SpeakerMediaPlayer::get_traits() {
498
+ auto traits = media_player::MediaPlayerTraits();
499
+ if (!this->single_pipeline_()) {
500
+ traits.set_supports_pause(true);
501
+ }
502
+
503
+ if (this->announcement_format_.has_value()) {
504
+ traits.get_supported_formats().push_back(this->announcement_format_.value());
505
+ }
506
+ if (this->media_format_.has_value()) {
507
+ traits.get_supported_formats().push_back(this->media_format_.value());
508
+ } else if (this->single_pipeline_() && this->announcement_format_.has_value()) {
509
+ // Only one pipeline is defined, so use the announcement format (if configured) for the default purpose
510
+ media_player::MediaPlayerSupportedFormat media_format = this->announcement_format_.value();
511
+ media_format.purpose = media_player::MediaPlayerFormatPurpose::PURPOSE_DEFAULT;
512
+ traits.get_supported_formats().push_back(media_format);
513
+ }
514
+
515
+ return traits;
516
+ };
517
+
518
+ void SpeakerMediaPlayer::save_volume_restore_state_() {
519
+ VolumeRestoreState volume_restore_state;
520
+ volume_restore_state.volume = this->volume;
521
+ volume_restore_state.is_muted = this->is_muted_;
522
+ this->pref_.save(&volume_restore_state);
523
+ }
524
+
525
+ void SpeakerMediaPlayer::set_mute_state_(bool mute_state) {
526
+ if (this->media_speaker_ != nullptr) {
527
+ this->media_speaker_->set_mute_state(mute_state);
528
+ }
529
+ if (this->announcement_speaker_ != nullptr) {
530
+ this->announcement_speaker_->set_mute_state(mute_state);
531
+ }
532
+
533
+ bool old_mute_state = this->is_muted_;
534
+ this->is_muted_ = mute_state;
535
+
536
+ this->save_volume_restore_state_();
537
+
538
+ if (old_mute_state != mute_state) {
539
+ if (mute_state) {
540
+ this->defer([this]() { this->mute_trigger_->trigger(); });
541
+ } else {
542
+ this->defer([this]() { this->unmute_trigger_->trigger(); });
543
+ }
544
+ }
545
+ }
546
+
547
+ void SpeakerMediaPlayer::set_volume_(float volume, bool publish) {
548
+ // Remap the volume to fit with in the configured limits
549
+ float bounded_volume = remap<float, float>(volume, 0.0f, 1.0f, this->volume_min_, this->volume_max_);
550
+
551
+ if (this->media_speaker_ != nullptr) {
552
+ this->media_speaker_->set_volume(bounded_volume);
553
+ }
554
+
555
+ if (this->announcement_speaker_ != nullptr) {
556
+ this->announcement_speaker_->set_volume(bounded_volume);
557
+ }
558
+
559
+ if (publish) {
560
+ this->volume = volume;
561
+ this->save_volume_restore_state_();
562
+ }
563
+
564
+ // Turn on the mute state if the volume is effectively zero, off otherwise
565
+ if (volume < 0.001) {
566
+ this->set_mute_state_(true);
567
+ } else {
568
+ this->set_mute_state_(false);
569
+ }
570
+
571
+ this->defer([this, volume]() { this->volume_trigger_->trigger(volume); });
572
+ }
573
+
574
+ } // namespace speaker
575
+ } // namespace esphome
576
+
577
+ #endif