esphome 2025.8.4__py3-none-any.whl → 2025.9.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +177 -105
- esphome/components/absolute_humidity/absolute_humidity.cpp +3 -5
- esphome/components/adc/__init__.py +1 -26
- esphome/components/adc/adc_sensor_esp32.cpp +29 -6
- esphome/components/adc/sensor.py +20 -0
- esphome/components/ags10/ags10.cpp +3 -18
- esphome/components/ags10/ags10.h +2 -12
- esphome/components/aht10/aht10.cpp +3 -3
- esphome/components/airthings_ble/__init__.py +2 -2
- esphome/components/alarm_control_panel/__init__.py +2 -2
- esphome/components/am2315c/am2315c.cpp +1 -17
- esphome/components/am2315c/am2315c.h +2 -3
- esphome/components/api/__init__.py +2 -2
- esphome/components/api/api_connection.cpp +38 -34
- esphome/components/api/api_connection.h +20 -40
- esphome/components/api/api_frame_helper.cpp +25 -25
- esphome/components/api/api_frame_helper.h +3 -3
- esphome/components/api/api_frame_helper_noise.cpp +75 -40
- esphome/components/api/api_frame_helper_noise.h +3 -7
- esphome/components/api/api_frame_helper_plaintext.cpp +17 -4
- esphome/components/api/api_frame_helper_plaintext.h +1 -4
- esphome/components/api/api_pb2.cpp +12 -2
- esphome/components/api/api_pb2.h +144 -143
- esphome/components/api/api_pb2_dump.cpp +6 -1
- esphome/components/api/api_pb2_service.cpp +0 -14
- esphome/components/api/api_pb2_service.h +1 -3
- esphome/components/api/client.py +5 -3
- esphome/components/api/proto.cpp +33 -37
- esphome/components/async_tcp/__init__.py +2 -2
- esphome/components/atm90e26/sensor.py +2 -0
- esphome/components/atm90e32/sensor.py +4 -2
- esphome/components/audio_adc/__init__.py +2 -2
- esphome/components/audio_dac/__init__.py +2 -2
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +1 -1
- esphome/components/bedjet/bedjet_hub.cpp +1 -1
- esphome/components/binary_sensor/__init__.py +2 -2
- esphome/components/binary_sensor/binary_sensor.cpp +13 -0
- esphome/components/binary_sensor/binary_sensor.h +4 -7
- esphome/components/bl0940/__init__.py +6 -1
- esphome/components/bl0940/bl0940.cpp +178 -41
- esphome/components/bl0940/bl0940.h +121 -76
- esphome/components/bl0940/button/__init__.py +27 -0
- esphome/components/bl0940/button/calibration_reset_button.cpp +20 -0
- esphome/components/bl0940/button/calibration_reset_button.h +19 -0
- esphome/components/bl0940/number/__init__.py +94 -0
- esphome/components/bl0940/number/calibration_number.cpp +29 -0
- esphome/components/bl0940/number/calibration_number.h +26 -0
- esphome/components/bl0940/sensor.py +151 -2
- esphome/components/bl0942/bl0942.cpp +1 -1
- esphome/components/ble_client/output/__init__.py +4 -4
- esphome/components/bluetooth_proxy/__init__.py +1 -1
- esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -1
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +15 -7
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +6 -3
- esphome/components/button/__init__.py +2 -2
- esphome/components/button/button.cpp +13 -0
- esphome/components/button/button.h +4 -7
- esphome/components/camera/buffer.h +18 -0
- esphome/components/camera/buffer_impl.cpp +20 -0
- esphome/components/camera/buffer_impl.h +26 -0
- esphome/components/camera/camera.h +43 -0
- esphome/components/camera/encoder.h +69 -0
- esphome/components/camera_encoder/__init__.py +62 -0
- esphome/components/camera_encoder/encoder_buffer_impl.cpp +23 -0
- esphome/components/camera_encoder/encoder_buffer_impl.h +25 -0
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +82 -0
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +39 -0
- esphome/components/captive_portal/__init__.py +2 -2
- esphome/components/captive_portal/captive_index.h +77 -97
- esphome/components/captive_portal/captive_portal.cpp +35 -12
- esphome/components/captive_portal/captive_portal.h +3 -3
- esphome/components/ccs811/ccs811.cpp +3 -3
- esphome/components/climate/__init__.py +2 -2
- esphome/components/climate/climate.cpp +1 -1
- esphome/components/cover/__init__.py +5 -5
- esphome/components/cover/cover.cpp +1 -1
- esphome/components/cover/cover.h +2 -2
- esphome/components/dallas_temp/dallas_temp.cpp +2 -2
- esphome/components/datetime/__init__.py +2 -2
- esphome/components/datetime/date_entity.h +2 -2
- esphome/components/datetime/datetime_entity.h +2 -2
- esphome/components/datetime/time_entity.h +2 -2
- esphome/components/debug/debug_esp32.cpp +1 -1
- esphome/components/display/__init__.py +4 -4
- esphome/components/duty_time/duty_time_sensor.cpp +1 -1
- esphome/components/esp32/__init__.py +0 -5
- esphome/components/esp32/gpio.cpp +27 -23
- esphome/components/esp32/gpio.h +26 -11
- esphome/components/esp32/preferences.cpp +8 -4
- esphome/components/esp32_ble/__init__.py +7 -2
- esphome/components/esp32_ble/ble_uuid.cpp +30 -9
- esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +4 -3
- esphome/components/esp32_ble_client/ble_client_base.cpp +7 -3
- esphome/components/esp32_ble_client/ble_client_base.h +8 -5
- esphome/components/esp32_ble_tracker/__init__.py +2 -2
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +11 -47
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -14
- esphome/components/esp8266/__init__.py +2 -2
- esphome/components/esp8266/core.cpp +2 -2
- esphome/components/esp8266/gpio.py +4 -4
- esphome/components/esp8266/preferences.cpp +30 -28
- esphome/components/esphome/ota/__init__.py +2 -2
- esphome/components/esphome/ota/ota_esphome.cpp +21 -19
- esphome/components/esphome/ota/ota_esphome.h +6 -5
- esphome/components/ethernet/__init__.py +7 -2
- esphome/components/ethernet/ethernet_component.cpp +49 -3
- esphome/components/ethernet/ethernet_component.h +2 -0
- esphome/components/event/__init__.py +2 -2
- esphome/components/event/event.h +4 -4
- esphome/components/factory_reset/button/factory_reset_button.cpp +18 -1
- esphome/components/factory_reset/button/factory_reset_button.h +6 -1
- esphome/components/factory_reset/switch/factory_reset_switch.cpp +18 -1
- esphome/components/factory_reset/switch/factory_reset_switch.h +5 -1
- esphome/components/fan/__init__.py +2 -2
- esphome/components/fan/fan.cpp +2 -1
- esphome/components/gdk101/gdk101.cpp +4 -4
- esphome/components/globals/__init__.py +2 -2
- esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +19 -18
- esphome/components/gpio_expander/cached_gpio.h +36 -16
- esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -5
- esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
- esphome/components/haier/haier_base.cpp +1 -1
- esphome/components/haier/hon_climate.cpp +1 -1
- esphome/components/hlw8012/hlw8012.cpp +5 -5
- esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +4 -4
- esphome/components/host/preferences.h +3 -2
- esphome/components/hte501/hte501.cpp +3 -21
- esphome/components/hte501/hte501.h +2 -3
- esphome/components/http_request/ota/__init__.py +2 -2
- esphome/components/i2c/__init__.py +2 -2
- esphome/components/i2c/i2c.cpp +13 -9
- esphome/components/i2c/i2c_bus.h +36 -6
- esphome/components/i2s_audio/__init__.py +8 -2
- esphome/components/i2s_audio/media_player/__init__.py +1 -1
- esphome/components/i2s_audio/microphone/__init__.py +1 -1
- esphome/components/i2s_audio/speaker/__init__.py +1 -1
- esphome/components/ina2xx_base/__init__.py +4 -2
- esphome/components/inkplate/__init__.py +1 -0
- esphome/components/inkplate/const.py +105 -0
- esphome/components/inkplate/display.py +238 -0
- esphome/components/{inkplate6 → inkplate}/inkplate.cpp +156 -74
- esphome/components/{inkplate6 → inkplate}/inkplate.h +28 -68
- esphome/components/inkplate6/__init__.py +0 -1
- esphome/components/inkplate6/display.py +2 -211
- esphome/components/integration/integration_sensor.cpp +1 -1
- esphome/components/json/__init__.py +2 -2
- esphome/components/lc709203f/lc709203f.cpp +4 -17
- esphome/components/lc709203f/lc709203f.h +2 -3
- esphome/components/ld2420/text_sensor/{text_sensor.cpp → ld2420_text_sensor.cpp} +1 -1
- esphome/components/ld2450/ld2450.cpp +1 -1
- esphome/components/libretiny/preferences.cpp +13 -5
- esphome/components/light/__init__.py +2 -2
- esphome/components/light/addressable_light_effect.h +7 -0
- esphome/components/light/base_light_effects.h +8 -0
- esphome/components/light/light_call.cpp +22 -20
- esphome/components/light/light_effect.cpp +36 -0
- esphome/components/light/light_effect.h +14 -0
- esphome/components/light/light_json_schema.cpp +9 -1
- esphome/components/light/light_state.cpp +2 -2
- esphome/components/light/light_state.h +38 -0
- esphome/components/lock/__init__.py +2 -2
- esphome/components/lock/lock.h +2 -2
- esphome/components/logger/__init__.py +2 -2
- esphome/components/logger/logger.cpp +25 -4
- esphome/components/logger/logger.h +1 -1
- esphome/components/logger/logger_esp32.cpp +16 -8
- esphome/components/logger/logger_esp8266.cpp +11 -3
- esphome/components/logger/logger_libretiny.cpp +13 -3
- esphome/components/logger/logger_rp2040.cpp +14 -3
- esphome/components/logger/logger_zephyr.cpp +15 -4
- esphome/components/lvgl/defines.py +1 -0
- esphome/components/lvgl/hello_world.py +96 -33
- esphome/components/lvgl/number/lvgl_number.h +1 -1
- esphome/components/lvgl/select/lvgl_select.h +1 -1
- esphome/components/lvgl/widgets/__init__.py +0 -1
- esphome/components/lvgl/widgets/spinbox.py +20 -11
- esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp +1 -1
- esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp +1 -1
- esphome/components/mapping/__init__.py +13 -5
- esphome/components/mapping/mapping.h +69 -0
- esphome/components/max17043/max17043.cpp +2 -2
- esphome/components/mcp23016/__init__.py +1 -0
- esphome/components/mcp23016/mcp23016.cpp +20 -5
- esphome/components/mcp23016/mcp23016.h +10 -4
- esphome/components/mcp23x08_base/mcp23x08_base.cpp +1 -1
- esphome/components/mcp23x17_base/mcp23x17_base.cpp +2 -2
- esphome/components/md5/md5.cpp +3 -2
- esphome/components/mdns/__init__.py +2 -2
- esphome/components/mdns/mdns_component.cpp +145 -54
- esphome/components/media_player/__init__.py +2 -2
- esphome/components/micro_wake_word/__init__.py +2 -2
- esphome/components/microphone/__init__.py +2 -2
- esphome/components/mipi/__init__.py +77 -33
- esphome/components/mipi_rgb/__init__.py +2 -0
- esphome/components/mipi_rgb/display.py +321 -0
- esphome/components/mipi_rgb/mipi_rgb.cpp +388 -0
- esphome/components/mipi_rgb/mipi_rgb.h +127 -0
- esphome/components/mipi_rgb/models/guition.py +24 -0
- esphome/components/mipi_rgb/models/lilygo.py +228 -0
- esphome/components/mipi_rgb/models/rpi.py +9 -0
- esphome/components/mipi_rgb/models/st7701s.py +214 -0
- esphome/components/mipi_rgb/models/waveshare.py +64 -0
- esphome/components/mipi_spi/models/jc.py +229 -0
- esphome/components/mlx90614/mlx90614.cpp +1 -16
- esphome/components/mlx90614/mlx90614.h +0 -1
- esphome/components/mqtt/__init__.py +2 -2
- esphome/components/mqtt/mqtt_sensor.cpp +7 -2
- esphome/components/ms5611/ms5611.cpp +7 -6
- esphome/components/network/__init__.py +2 -2
- esphome/components/nextion/nextion_upload.cpp +4 -1
- esphome/components/nrf52/__init__.py +49 -6
- esphome/components/nrf52/const.py +1 -0
- esphome/components/nrf52/dfu.cpp +51 -0
- esphome/components/nrf52/dfu.h +24 -0
- esphome/components/ntc/ntc.cpp +1 -1
- esphome/components/number/__init__.py +2 -2
- esphome/components/number/automation.cpp +1 -1
- esphome/components/number/number.cpp +21 -0
- esphome/components/number/number.h +4 -13
- esphome/components/opentherm/hub.h +6 -6
- esphome/components/opentherm/number/{number.cpp → opentherm_number.cpp} +2 -2
- esphome/components/opentherm/output/{output.cpp → opentherm_output.cpp} +1 -1
- esphome/components/opentherm/switch/{switch.cpp → opentherm_switch.cpp} +1 -1
- esphome/components/openthread/openthread.cpp +41 -7
- esphome/components/openthread/openthread.h +11 -0
- esphome/components/ota/__init__.py +2 -2
- esphome/components/pca6416a/__init__.py +1 -0
- esphome/components/pca6416a/pca6416a.cpp +20 -5
- esphome/components/pca6416a/pca6416a.h +12 -5
- esphome/components/pca9554/__init__.py +2 -1
- esphome/components/pca9554/pca9554.cpp +12 -18
- esphome/components/pca9554/pca9554.h +10 -9
- esphome/components/pcf8574/__init__.py +1 -0
- esphome/components/pcf8574/pcf8574.cpp +14 -5
- esphome/components/pcf8574/pcf8574.h +13 -6
- esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +7 -7
- esphome/components/pipsolar/__init__.py +3 -3
- esphome/components/pipsolar/output/__init__.py +4 -4
- esphome/components/pulse_width/pulse_width.cpp +2 -2
- esphome/components/qmp6988/qmp6988.cpp +81 -126
- esphome/components/qmp6988/qmp6988.h +31 -37
- esphome/components/radon_eye_ble/__init__.py +2 -2
- esphome/components/remote_base/__init__.py +6 -8
- esphome/components/rotary_encoder/rotary_encoder.cpp +1 -1
- esphome/components/rp2040/__init__.py +2 -2
- esphome/components/runtime_stats/runtime_stats.cpp +10 -23
- esphome/components/runtime_stats/runtime_stats.h +4 -10
- esphome/components/safe_mode/__init__.py +2 -2
- esphome/components/safe_mode/safe_mode.cpp +33 -31
- esphome/components/script/script.cpp +6 -0
- esphome/components/script/script.h +19 -5
- esphome/components/sdm_meter/sensor.py +3 -1
- esphome/components/select/__init__.py +2 -2
- esphome/components/select/select.h +2 -2
- esphome/components/sen5x/sen5x.cpp +57 -55
- esphome/components/sen5x/sen5x.h +21 -15
- esphome/components/sen5x/sensor.py +67 -44
- esphome/components/sensirion_common/i2c_sensirion.cpp +18 -47
- esphome/components/sensirion_common/i2c_sensirion.h +39 -55
- esphome/components/sensor/__init__.py +2 -2
- esphome/components/sensor/automation.h +1 -1
- esphome/components/sensor/sensor.cpp +34 -6
- esphome/components/sensor/sensor.h +4 -21
- esphome/components/sgp30/sgp30.cpp +34 -35
- esphome/components/sgp30/sgp30.h +11 -10
- esphome/components/sgp4x/sgp4x.cpp +2 -2
- esphome/components/shelly_dimmer/light.py +7 -7
- esphome/components/sht4x/sht4x.cpp +1 -1
- esphome/components/sntp/sntp_component.cpp +36 -9
- esphome/components/sntp/sntp_component.h +7 -0
- esphome/components/sound_level/sound_level.cpp +1 -1
- esphome/components/speaker/__init__.py +2 -2
- esphome/components/speaker/media_player/__init__.py +2 -2
- esphome/components/speaker/media_player/speaker_media_player.cpp +1 -1
- esphome/components/spi/__init__.py +2 -2
- esphome/components/sprinkler/sprinkler.cpp +1 -1
- esphome/components/sps30/sps30.cpp +18 -23
- esphome/components/sps30/sps30.h +3 -3
- esphome/components/status_led/__init__.py +2 -2
- esphome/components/stepper/__init__.py +2 -2
- esphome/components/switch/__init__.py +2 -2
- esphome/components/switch/switch.cpp +5 -5
- esphome/components/sx1509/__init__.py +1 -1
- esphome/components/sx1509/sx1509.cpp +12 -7
- esphome/components/sx1509/sx1509.h +11 -4
- esphome/components/tca9555/tca9555.cpp +5 -5
- esphome/components/tee501/tee501.cpp +2 -21
- esphome/components/tee501/tee501.h +2 -4
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +1 -1
- esphome/components/template/datetime/template_date.cpp +1 -1
- esphome/components/template/datetime/template_datetime.cpp +2 -2
- esphome/components/template/datetime/template_time.cpp +1 -1
- esphome/components/template/number/template_number.cpp +1 -1
- esphome/components/template/select/template_select.cpp +1 -1
- esphome/components/template/text/template_text.cpp +1 -1
- esphome/components/text/__init__.py +2 -2
- esphome/components/text/text.h +2 -2
- esphome/components/text_sensor/__init__.py +2 -2
- esphome/components/text_sensor/text_sensor.h +4 -4
- esphome/components/thermostat/climate.py +11 -7
- esphome/components/thermostat/thermostat_climate.cpp +237 -206
- esphome/components/thermostat/thermostat_climate.h +52 -41
- esphome/components/time/__init__.py +2 -2
- esphome/components/tmp1075/tmp1075.cpp +1 -1
- esphome/components/total_daily_energy/total_daily_energy.cpp +1 -1
- esphome/components/touchscreen/__init__.py +2 -2
- esphome/components/tuya/number/tuya_number.cpp +1 -1
- esphome/components/udp/udp_component.cpp +3 -3
- esphome/components/ufire_ec/ufire_ec.cpp +4 -4
- esphome/components/ufire_ise/ufire_ise.cpp +4 -4
- esphome/components/update/__init__.py +2 -2
- esphome/components/usb_uart/usb_uart.cpp +1 -1
- esphome/components/valve/__init__.py +5 -5
- esphome/components/valve/valve.cpp +1 -1
- esphome/components/valve/valve.h +2 -2
- esphome/components/wake_on_lan/wake_on_lan.cpp +2 -2
- esphome/components/waveshare_epaper/waveshare_213v3.cpp +1 -1
- esphome/components/web_server/__init__.py +2 -2
- esphome/components/web_server/ota/__init__.py +2 -2
- esphome/components/web_server/ota/ota_web_server.cpp +11 -0
- esphome/components/web_server/server_index_v2.h +149 -149
- esphome/components/web_server/web_server.cpp +58 -12
- esphome/components/web_server_base/__init__.py +2 -2
- esphome/components/wifi/__init__.py +5 -5
- esphome/components/wifi/wifi_component.cpp +4 -4
- esphome/components/wifi/wifi_component_esp_idf.cpp +2 -0
- esphome/components/wifi_info/wifi_info_text_sensor.h +3 -2
- esphome/config_validation.py +2 -2
- esphome/const.py +3 -1
- esphome/core/__init__.py +1 -0
- esphome/core/application.cpp +89 -51
- esphome/core/application.h +1 -0
- esphome/core/component.cpp +41 -19
- esphome/core/component.h +17 -13
- esphome/core/config.py +7 -7
- esphome/core/defines.h +4 -0
- esphome/core/entity_base.cpp +22 -8
- esphome/core/entity_base.h +43 -0
- esphome/core/helpers.cpp +34 -20
- esphome/core/helpers.h +33 -3
- esphome/core/ring_buffer.cpp +6 -2
- esphome/core/ring_buffer.h +2 -1
- esphome/core/scheduler.cpp +178 -97
- esphome/core/scheduler.h +67 -36
- esphome/core/time.cpp +6 -20
- esphome/coroutine.py +80 -3
- esphome/cpp_generator.py +13 -0
- esphome/cpp_helpers.py +2 -2
- esphome/dashboard/web_server.py +67 -10
- esphome/espota2.py +13 -6
- esphome/helpers.py +68 -83
- esphome/resolver.py +67 -0
- esphome/util.py +9 -6
- esphome/wizard.py +39 -26
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/METADATA +9 -9
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/RECORD +364 -333
- /esphome/components/ld2420/text_sensor/{text_sensor.h → ld2420_text_sensor.h} +0 -0
- /esphome/components/opentherm/number/{number.h → opentherm_number.h} +0 -0
- /esphome/components/opentherm/output/{output.h → opentherm_output.h} +0 -0
- /esphome/components/opentherm/switch/{switch.h → opentherm_switch.h} +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/WHEEL +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/entry_points.txt +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b2.dist-info}/top_level.txt +0 -0
esphome/coroutine.py
CHANGED
@@ -42,7 +42,10 @@ Here everything is combined in `yield` expressions. You await other coroutines u
|
|
42
42
|
the last `yield` expression defines what is returned.
|
43
43
|
"""
|
44
44
|
|
45
|
+
from __future__ import annotations
|
46
|
+
|
45
47
|
from collections.abc import Awaitable, Callable, Generator, Iterator
|
48
|
+
import enum
|
46
49
|
import functools
|
47
50
|
import heapq
|
48
51
|
import inspect
|
@@ -53,6 +56,79 @@ from typing import Any
|
|
53
56
|
_LOGGER = logging.getLogger(__name__)
|
54
57
|
|
55
58
|
|
59
|
+
class CoroPriority(enum.IntEnum):
|
60
|
+
"""Execution priority stages for ESPHome code generation.
|
61
|
+
|
62
|
+
Higher values run first. These stages ensure proper dependency
|
63
|
+
resolution during code generation.
|
64
|
+
"""
|
65
|
+
|
66
|
+
# Platform initialization - must run first
|
67
|
+
# Examples: esp32, esp8266, rp2040
|
68
|
+
PLATFORM = 1000
|
69
|
+
|
70
|
+
# Network infrastructure setup
|
71
|
+
# Examples: network (201)
|
72
|
+
NETWORK = 201
|
73
|
+
|
74
|
+
# Network transport layer
|
75
|
+
# Examples: async_tcp (200)
|
76
|
+
NETWORK_TRANSPORT = 200
|
77
|
+
|
78
|
+
# Core system components
|
79
|
+
# Examples: esphome core, most entity base components (cover, update, datetime,
|
80
|
+
# valve, alarm_control_panel, lock, event, binary_sensor, button, climate, fan,
|
81
|
+
# light, media_player, number, select, sensor, switch, text_sensor, text),
|
82
|
+
# microphone, speaker, audio_dac, touchscreen, stepper
|
83
|
+
CORE = 100
|
84
|
+
|
85
|
+
# Diagnostic and debugging systems
|
86
|
+
# Examples: logger (90)
|
87
|
+
DIAGNOSTICS = 90
|
88
|
+
|
89
|
+
# Status and monitoring systems
|
90
|
+
# Examples: status_led (80)
|
91
|
+
STATUS = 80
|
92
|
+
|
93
|
+
# Communication protocols and services
|
94
|
+
# Examples: web_server_base (65), captive_portal (64), wifi (60), ethernet (60),
|
95
|
+
# mdns (55), ota_updates (54), web_server_ota (52)
|
96
|
+
COMMUNICATION = 60
|
97
|
+
|
98
|
+
# Application-level services
|
99
|
+
# Examples: safe_mode (50)
|
100
|
+
APPLICATION = 50
|
101
|
+
|
102
|
+
# Web and UI services
|
103
|
+
# Examples: web_server (40)
|
104
|
+
WEB = 40
|
105
|
+
|
106
|
+
# Automations and user logic
|
107
|
+
# Examples: esphome core automations (30)
|
108
|
+
AUTOMATION = 30
|
109
|
+
|
110
|
+
# Bus and peripheral setup
|
111
|
+
# Examples: i2c (1)
|
112
|
+
BUS = 1
|
113
|
+
|
114
|
+
# Standard component priority (default)
|
115
|
+
# Components without explicit priority run at 0
|
116
|
+
COMPONENT = 0
|
117
|
+
|
118
|
+
# Components that need others to be registered first
|
119
|
+
# Examples: globals (-100)
|
120
|
+
LATE = -100
|
121
|
+
|
122
|
+
# Platform-specific workarounds and fixes
|
123
|
+
# Examples: add_arduino_global_workaround (-999), esp8266 pin states (-999)
|
124
|
+
WORKAROUNDS = -999
|
125
|
+
|
126
|
+
# Final setup that requires all components to be registered
|
127
|
+
# Examples: add_includes, _add_platformio_options, _add_platform_defines (all -1000),
|
128
|
+
# esp32_ble_tracker feature defines (-1000)
|
129
|
+
FINAL = -1000
|
130
|
+
|
131
|
+
|
56
132
|
def coroutine(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
|
57
133
|
"""Decorator to apply to methods to convert them to ESPHome coroutines."""
|
58
134
|
if getattr(func, "_esphome_coroutine", False):
|
@@ -95,15 +171,16 @@ def coroutine(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
|
|
95
171
|
return coro
|
96
172
|
|
97
173
|
|
98
|
-
def coroutine_with_priority(priority: float):
|
174
|
+
def coroutine_with_priority(priority: float | CoroPriority):
|
99
175
|
"""Decorator to apply to functions to convert them to ESPHome coroutines.
|
100
176
|
|
101
177
|
:param priority: priority with which to schedule the coroutine, higher priorities run first.
|
178
|
+
Can be a float or a CoroPriority enum value.
|
102
179
|
"""
|
103
180
|
|
104
181
|
def decorator(func):
|
105
182
|
coro = coroutine(func)
|
106
|
-
coro.priority = priority
|
183
|
+
coro.priority = float(priority)
|
107
184
|
return coro
|
108
185
|
|
109
186
|
return decorator
|
@@ -173,7 +250,7 @@ class _Task:
|
|
173
250
|
self.iterator = iterator
|
174
251
|
self.original_function = original_function
|
175
252
|
|
176
|
-
def with_priority(self, priority: float) ->
|
253
|
+
def with_priority(self, priority: float) -> _Task:
|
177
254
|
return _Task(priority, self.id_number, self.iterator, self.original_function)
|
178
255
|
|
179
256
|
@property
|
esphome/cpp_generator.py
CHANGED
@@ -253,6 +253,19 @@ class StringLiteral(Literal):
|
|
253
253
|
return cpp_string_escape(self.string)
|
254
254
|
|
255
255
|
|
256
|
+
class LogStringLiteral(Literal):
|
257
|
+
"""A string literal that uses LOG_STR() macro for flash storage on ESP8266."""
|
258
|
+
|
259
|
+
__slots__ = ("string",)
|
260
|
+
|
261
|
+
def __init__(self, string: str) -> None:
|
262
|
+
super().__init__()
|
263
|
+
self.string = string
|
264
|
+
|
265
|
+
def __str__(self) -> str:
|
266
|
+
return f"LOG_STR({cpp_string_escape(self.string)})"
|
267
|
+
|
268
|
+
|
256
269
|
class IntLiteral(Literal):
|
257
270
|
__slots__ = ("i",)
|
258
271
|
|
esphome/cpp_helpers.py
CHANGED
@@ -9,7 +9,7 @@ from esphome.const import (
|
|
9
9
|
)
|
10
10
|
from esphome.core import CORE, ID, coroutine
|
11
11
|
from esphome.coroutine import FakeAwaitable
|
12
|
-
from esphome.cpp_generator import add, get_variable
|
12
|
+
from esphome.cpp_generator import LogStringLiteral, add, get_variable
|
13
13
|
from esphome.cpp_types import App
|
14
14
|
from esphome.types import ConfigFragmentType, ConfigType
|
15
15
|
from esphome.util import Registry, RegistryEntry
|
@@ -76,7 +76,7 @@ async def register_component(var, config):
|
|
76
76
|
"Error while finding name of component, please report this", exc_info=e
|
77
77
|
)
|
78
78
|
if name is not None:
|
79
|
-
add(var.set_component_source(name))
|
79
|
+
add(var.set_component_source(LogStringLiteral(name)))
|
80
80
|
|
81
81
|
add(App.register_component(var))
|
82
82
|
return var
|
esphome/dashboard/web_server.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import asyncio
|
4
4
|
import base64
|
5
|
+
import binascii
|
5
6
|
from collections.abc import Callable, Iterable
|
6
7
|
import datetime
|
7
8
|
import functools
|
@@ -490,7 +491,17 @@ class WizardRequestHandler(BaseHandler):
|
|
490
491
|
kwargs = {
|
491
492
|
k: v
|
492
493
|
for k, v in json.loads(self.request.body.decode()).items()
|
493
|
-
if k
|
494
|
+
if k
|
495
|
+
in (
|
496
|
+
"type",
|
497
|
+
"name",
|
498
|
+
"platform",
|
499
|
+
"board",
|
500
|
+
"ssid",
|
501
|
+
"psk",
|
502
|
+
"password",
|
503
|
+
"file_content",
|
504
|
+
)
|
494
505
|
}
|
495
506
|
if not kwargs["name"]:
|
496
507
|
self.set_status(422)
|
@@ -498,19 +509,65 @@ class WizardRequestHandler(BaseHandler):
|
|
498
509
|
self.write(json.dumps({"error": "Name is required"}))
|
499
510
|
return
|
500
511
|
|
512
|
+
if "type" not in kwargs:
|
513
|
+
# Default to basic wizard type for backwards compatibility
|
514
|
+
kwargs["type"] = "basic"
|
515
|
+
|
501
516
|
kwargs["friendly_name"] = kwargs["name"]
|
502
517
|
kwargs["name"] = friendly_name_slugify(kwargs["friendly_name"])
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
518
|
+
if kwargs["type"] == "basic":
|
519
|
+
kwargs["ota_password"] = secrets.token_hex(16)
|
520
|
+
noise_psk = secrets.token_bytes(32)
|
521
|
+
kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode()
|
522
|
+
elif kwargs["type"] == "upload":
|
523
|
+
try:
|
524
|
+
kwargs["file_text"] = base64.b64decode(kwargs["file_content"]).decode(
|
525
|
+
"utf-8"
|
526
|
+
)
|
527
|
+
except (binascii.Error, UnicodeDecodeError):
|
528
|
+
self.set_status(422)
|
529
|
+
self.set_header("content-type", "application/json")
|
530
|
+
self.write(
|
531
|
+
json.dumps({"error": "The uploaded file is not correctly encoded."})
|
532
|
+
)
|
533
|
+
return
|
534
|
+
elif kwargs["type"] != "empty":
|
535
|
+
self.set_status(422)
|
536
|
+
self.set_header("content-type", "application/json")
|
537
|
+
self.write(
|
538
|
+
json.dumps(
|
539
|
+
{"error": f"Invalid wizard type specified: {kwargs['type']}"}
|
540
|
+
)
|
541
|
+
)
|
542
|
+
return
|
507
543
|
filename = f"{kwargs['name']}.yaml"
|
508
544
|
destination = settings.rel_path(filename)
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
545
|
+
|
546
|
+
# Check if destination file already exists
|
547
|
+
if os.path.exists(destination):
|
548
|
+
self.set_status(409) # Conflict status code
|
549
|
+
self.set_header("content-type", "application/json")
|
550
|
+
self.write(
|
551
|
+
json.dumps({"error": f"Configuration file '{filename}' already exists"})
|
552
|
+
)
|
553
|
+
self.finish()
|
554
|
+
return
|
555
|
+
|
556
|
+
success = wizard.wizard_write(path=destination, **kwargs)
|
557
|
+
if success:
|
558
|
+
self.set_status(200)
|
559
|
+
self.set_header("content-type", "application/json")
|
560
|
+
self.write(json.dumps({"configuration": filename}))
|
561
|
+
self.finish()
|
562
|
+
else:
|
563
|
+
self.set_status(500)
|
564
|
+
self.set_header("content-type", "application/json")
|
565
|
+
self.write(
|
566
|
+
json.dumps(
|
567
|
+
{"error": "Failed to write configuration, see logs for details"}
|
568
|
+
)
|
569
|
+
)
|
570
|
+
self.finish()
|
514
571
|
|
515
572
|
|
516
573
|
class ImportRequestHandler(BaseHandler):
|
esphome/espota2.py
CHANGED
@@ -308,8 +308,12 @@ def perform_ota(
|
|
308
308
|
time.sleep(1)
|
309
309
|
|
310
310
|
|
311
|
-
def run_ota_impl_(
|
311
|
+
def run_ota_impl_(
|
312
|
+
remote_host: str | list[str], remote_port: int, password: str, filename: str
|
313
|
+
) -> tuple[int, str | None]:
|
314
|
+
# Handle both single host and list of hosts
|
312
315
|
try:
|
316
|
+
# Resolve all hosts at once for parallel DNS resolution
|
313
317
|
res = resolve_ip_address(remote_host, remote_port)
|
314
318
|
except EsphomeError as err:
|
315
319
|
_LOGGER.error(
|
@@ -340,19 +344,22 @@ def run_ota_impl_(remote_host, remote_port, password, filename):
|
|
340
344
|
perform_ota(sock, password, file_handle, filename)
|
341
345
|
except OTAError as err:
|
342
346
|
_LOGGER.error(str(err))
|
343
|
-
return 1
|
347
|
+
return 1, None
|
344
348
|
finally:
|
345
349
|
sock.close()
|
346
350
|
|
347
|
-
|
351
|
+
# Successfully uploaded to sa[0]
|
352
|
+
return 0, sa[0]
|
348
353
|
|
349
354
|
_LOGGER.error("Connection failed.")
|
350
|
-
return 1
|
355
|
+
return 1, None
|
351
356
|
|
352
357
|
|
353
|
-
def run_ota(
|
358
|
+
def run_ota(
|
359
|
+
remote_host: str | list[str], remote_port: int, password: str, filename: str
|
360
|
+
) -> tuple[int, str | None]:
|
354
361
|
try:
|
355
362
|
return run_ota_impl_(remote_host, remote_port, password, filename)
|
356
363
|
except OTAError as err:
|
357
364
|
_LOGGER.error(err)
|
358
|
-
return 1
|
365
|
+
return 1, None
|
esphome/helpers.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import codecs
|
2
4
|
from contextlib import suppress
|
3
5
|
import ipaddress
|
@@ -11,6 +13,18 @@ from urllib.parse import urlparse
|
|
11
13
|
|
12
14
|
from esphome.const import __version__ as ESPHOME_VERSION
|
13
15
|
|
16
|
+
# Type aliases for socket address information
|
17
|
+
AddrInfo = tuple[
|
18
|
+
int, # family (AF_INET, AF_INET6, etc.)
|
19
|
+
int, # type (SOCK_STREAM, SOCK_DGRAM, etc.)
|
20
|
+
int, # proto (IPPROTO_TCP, etc.)
|
21
|
+
str, # canonname
|
22
|
+
tuple[str, int] | tuple[str, int, int, int], # sockaddr (IPv4 or IPv6)
|
23
|
+
]
|
24
|
+
IPv4SockAddr = tuple[str, int] # (host, port)
|
25
|
+
IPv6SockAddr = tuple[str, int, int, int] # (host, port, flowinfo, scope_id)
|
26
|
+
SockAddr = IPv4SockAddr | IPv6SockAddr
|
27
|
+
|
14
28
|
_LOGGER = logging.getLogger(__name__)
|
15
29
|
|
16
30
|
IS_MACOS = platform.system() == "Darwin"
|
@@ -147,32 +161,7 @@ def is_ip_address(host):
|
|
147
161
|
return False
|
148
162
|
|
149
163
|
|
150
|
-
def
|
151
|
-
from esphome.core import EsphomeError
|
152
|
-
from esphome.zeroconf import EsphomeZeroconf
|
153
|
-
|
154
|
-
try:
|
155
|
-
zc = EsphomeZeroconf()
|
156
|
-
except Exception as err:
|
157
|
-
raise EsphomeError(
|
158
|
-
"Cannot start mDNS sockets, is this a docker container without "
|
159
|
-
"host network mode?"
|
160
|
-
) from err
|
161
|
-
try:
|
162
|
-
info = zc.resolve_host(f"{host}.")
|
163
|
-
except Exception as err:
|
164
|
-
raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err
|
165
|
-
finally:
|
166
|
-
zc.close()
|
167
|
-
if info is None:
|
168
|
-
raise EsphomeError(
|
169
|
-
"Error resolving address with mDNS: Did not respond. "
|
170
|
-
"Maybe the device is offline."
|
171
|
-
)
|
172
|
-
return info
|
173
|
-
|
174
|
-
|
175
|
-
def addr_preference_(res):
|
164
|
+
def addr_preference_(res: AddrInfo) -> int:
|
176
165
|
# Trivial alternative to RFC6724 sorting. Put sane IPv6 first, then
|
177
166
|
# Legacy IP, then IPv6 link-local addresses without an actual link.
|
178
167
|
sa = res[4]
|
@@ -184,66 +173,70 @@ def addr_preference_(res):
|
|
184
173
|
return 1
|
185
174
|
|
186
175
|
|
187
|
-
def resolve_ip_address(host, port):
|
176
|
+
def resolve_ip_address(host: str | list[str], port: int) -> list[AddrInfo]:
|
188
177
|
import socket
|
189
178
|
|
190
|
-
from esphome.core import EsphomeError
|
191
|
-
|
192
179
|
# There are five cases here. The host argument could be one of:
|
193
180
|
# • a *list* of IP addresses discovered by MQTT,
|
194
181
|
# • a single IP address specified by the user,
|
195
182
|
# • a .local hostname to be resolved by mDNS,
|
196
183
|
# • a normal hostname to be resolved in DNS, or
|
197
184
|
# • A URL from which we should extract the hostname.
|
198
|
-
|
199
|
-
|
200
|
-
# string form which need to be converted to a 5-tuple to be used
|
201
|
-
# for the socket connection attempt. The easiest way to construct
|
202
|
-
# those is to pass the IP address string to getaddrinfo(). Which,
|
203
|
-
# coincidentally, is how we do hostname lookups in the other cases
|
204
|
-
# too. So first build a list which contains either IP addresses or
|
205
|
-
# a single hostname, then call getaddrinfo() on each element of
|
206
|
-
# that list.
|
207
|
-
|
208
|
-
errs = []
|
185
|
+
|
186
|
+
hosts: list[str]
|
209
187
|
if isinstance(host, list):
|
210
|
-
|
211
|
-
elif is_ip_address(host):
|
212
|
-
addr_list = [host]
|
188
|
+
hosts = host
|
213
189
|
else:
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
190
|
+
if not is_ip_address(host):
|
191
|
+
url = urlparse(host)
|
192
|
+
if url.scheme != "":
|
193
|
+
host = url.hostname
|
194
|
+
hosts = [host]
|
195
|
+
|
196
|
+
res: list[AddrInfo] = []
|
197
|
+
if all(is_ip_address(h) for h in hosts):
|
198
|
+
# Fast path: all are IP addresses, use socket.getaddrinfo with AI_NUMERICHOST
|
199
|
+
for addr in hosts:
|
220
200
|
try:
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
#
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
201
|
+
res += socket.getaddrinfo(
|
202
|
+
addr, port, proto=socket.IPPROTO_TCP, flags=socket.AI_NUMERICHOST
|
203
|
+
)
|
204
|
+
except OSError:
|
205
|
+
_LOGGER.debug("Failed to parse IP address '%s'", addr)
|
206
|
+
# Sort by preference
|
207
|
+
res.sort(key=addr_preference_)
|
208
|
+
return res
|
209
|
+
|
210
|
+
from esphome.resolver import AsyncResolver
|
211
|
+
|
212
|
+
resolver = AsyncResolver(hosts, port)
|
213
|
+
addr_infos = resolver.resolve()
|
214
|
+
# Convert aioesphomeapi AddrInfo to our format
|
215
|
+
for addr_info in addr_infos:
|
216
|
+
sockaddr = addr_info.sockaddr
|
217
|
+
if addr_info.family == socket.AF_INET6:
|
218
|
+
# IPv6
|
219
|
+
sockaddr_tuple = (
|
220
|
+
sockaddr.address,
|
221
|
+
sockaddr.port,
|
222
|
+
sockaddr.flowinfo,
|
223
|
+
sockaddr.scope_id,
|
224
|
+
)
|
225
|
+
else:
|
226
|
+
# IPv4
|
227
|
+
sockaddr_tuple = (sockaddr.address, sockaddr.port)
|
228
|
+
|
229
|
+
res.append(
|
230
|
+
(
|
231
|
+
addr_info.family,
|
232
|
+
addr_info.type,
|
233
|
+
addr_info.proto,
|
234
|
+
"", # canonname
|
235
|
+
sockaddr_tuple,
|
236
|
+
)
|
237
|
+
)
|
244
238
|
|
245
|
-
#
|
246
|
-
# the link. Put those last in the list to be attempted.
|
239
|
+
# Sort by preference
|
247
240
|
res.sort(key=addr_preference_)
|
248
241
|
return res
|
249
242
|
|
@@ -262,15 +255,7 @@ def sort_ip_addresses(address_list: list[str]) -> list[str]:
|
|
262
255
|
|
263
256
|
# First "resolve" all the IP addresses to getaddrinfo() tuples of the form
|
264
257
|
# (family, type, proto, canonname, sockaddr)
|
265
|
-
res: list[
|
266
|
-
tuple[
|
267
|
-
int,
|
268
|
-
int,
|
269
|
-
int,
|
270
|
-
str | None,
|
271
|
-
tuple[str, int] | tuple[str, int, int, int],
|
272
|
-
]
|
273
|
-
] = []
|
258
|
+
res: list[AddrInfo] = []
|
274
259
|
for addr in address_list:
|
275
260
|
# This should always work as these are supposed to be IP addresses
|
276
261
|
try:
|
esphome/resolver.py
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
"""DNS resolver for ESPHome using aioesphomeapi."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import threading
|
7
|
+
|
8
|
+
from aioesphomeapi.core import ResolveAPIError, ResolveTimeoutAPIError
|
9
|
+
import aioesphomeapi.host_resolver as hr
|
10
|
+
|
11
|
+
from esphome.core import EsphomeError
|
12
|
+
|
13
|
+
RESOLVE_TIMEOUT = 10.0 # seconds
|
14
|
+
|
15
|
+
|
16
|
+
class AsyncResolver(threading.Thread):
|
17
|
+
"""Resolver using aioesphomeapi that runs in a thread for faster results.
|
18
|
+
|
19
|
+
This resolver uses aioesphomeapi's async_resolve_host to handle DNS resolution,
|
20
|
+
including proper .local domain fallback. Running in a thread allows us to get
|
21
|
+
the result immediately without waiting for asyncio.run() to complete its
|
22
|
+
cleanup cycle, which can take significant time.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, hosts: list[str], port: int) -> None:
|
26
|
+
"""Initialize the resolver."""
|
27
|
+
super().__init__(daemon=True)
|
28
|
+
self.hosts = hosts
|
29
|
+
self.port = port
|
30
|
+
self.result: list[hr.AddrInfo] | None = None
|
31
|
+
self.exception: Exception | None = None
|
32
|
+
self.event = threading.Event()
|
33
|
+
|
34
|
+
async def _resolve(self) -> None:
|
35
|
+
"""Resolve hostnames to IP addresses."""
|
36
|
+
try:
|
37
|
+
self.result = await hr.async_resolve_host(
|
38
|
+
self.hosts, self.port, timeout=RESOLVE_TIMEOUT
|
39
|
+
)
|
40
|
+
except Exception as e: # pylint: disable=broad-except
|
41
|
+
# We need to catch all exceptions to ensure the event is set
|
42
|
+
# Otherwise the thread could hang forever
|
43
|
+
self.exception = e
|
44
|
+
finally:
|
45
|
+
self.event.set()
|
46
|
+
|
47
|
+
def run(self) -> None:
|
48
|
+
"""Run the DNS resolution."""
|
49
|
+
asyncio.run(self._resolve())
|
50
|
+
|
51
|
+
def resolve(self) -> list[hr.AddrInfo]:
|
52
|
+
"""Start the thread and wait for the result."""
|
53
|
+
self.start()
|
54
|
+
|
55
|
+
if not self.event.wait(
|
56
|
+
timeout=RESOLVE_TIMEOUT + 1.0
|
57
|
+
): # Give it 1 second more than the resolver timeout
|
58
|
+
raise EsphomeError("Timeout resolving IP address")
|
59
|
+
|
60
|
+
if exc := self.exception:
|
61
|
+
if isinstance(exc, ResolveTimeoutAPIError):
|
62
|
+
raise EsphomeError(f"Timeout resolving IP address: {exc}") from exc
|
63
|
+
if isinstance(exc, ResolveAPIError):
|
64
|
+
raise EsphomeError(f"Error resolving IP address: {exc}") from exc
|
65
|
+
raise exc
|
66
|
+
|
67
|
+
return self.result
|
esphome/util.py
CHANGED
@@ -272,12 +272,15 @@ class OrderedDict(collections.OrderedDict):
|
|
272
272
|
return dict(self).__repr__()
|
273
273
|
|
274
274
|
|
275
|
-
def list_yaml_files(
|
276
|
-
files =
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
275
|
+
def list_yaml_files(configs: list[str]) -> list[str]:
|
276
|
+
files: list[str] = []
|
277
|
+
for config in configs:
|
278
|
+
if os.path.isfile(config):
|
279
|
+
files.append(config)
|
280
|
+
else:
|
281
|
+
files.extend(os.path.join(config, p) for p in os.listdir(config))
|
282
|
+
files = filter_yaml_files(files)
|
283
|
+
return sorted(files)
|
281
284
|
|
282
285
|
|
283
286
|
def filter_yaml_files(files: list[str]) -> list[str]:
|
esphome/wizard.py
CHANGED
@@ -189,32 +189,45 @@ def wizard_write(path, **kwargs):
|
|
189
189
|
from esphome.components.rtl87xx import boards as rtl87xx_boards
|
190
190
|
|
191
191
|
name = kwargs["name"]
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
192
|
+
if kwargs["type"] == "empty":
|
193
|
+
file_text = ""
|
194
|
+
# Will be updated later after editing the file
|
195
|
+
hardware = "UNKNOWN"
|
196
|
+
elif kwargs["type"] == "upload":
|
197
|
+
file_text = kwargs["file_text"]
|
198
|
+
hardware = "UNKNOWN"
|
199
|
+
else: # "basic"
|
200
|
+
board = kwargs["board"]
|
201
|
+
|
202
|
+
for key in ("ssid", "psk", "password", "ota_password"):
|
203
|
+
if key in kwargs:
|
204
|
+
kwargs[key] = sanitize_double_quotes(kwargs[key])
|
205
|
+
if "platform" not in kwargs:
|
206
|
+
if board in esp8266_boards.BOARDS:
|
207
|
+
platform = "ESP8266"
|
208
|
+
elif board in esp32_boards.BOARDS:
|
209
|
+
platform = "ESP32"
|
210
|
+
elif board in rp2040_boards.BOARDS:
|
211
|
+
platform = "RP2040"
|
212
|
+
elif board in bk72xx_boards.BOARDS:
|
213
|
+
platform = "BK72XX"
|
214
|
+
elif board in ln882x_boards.BOARDS:
|
215
|
+
platform = "LN882X"
|
216
|
+
elif board in rtl87xx_boards.BOARDS:
|
217
|
+
platform = "RTL87XX"
|
218
|
+
else:
|
219
|
+
safe_print(color(AnsiFore.RED, f'The board "{board}" is unknown.'))
|
220
|
+
return False
|
221
|
+
kwargs["platform"] = platform
|
222
|
+
hardware = kwargs["platform"]
|
223
|
+
file_text = wizard_file(**kwargs)
|
224
|
+
|
225
|
+
# Check if file already exists to prevent overwriting
|
226
|
+
if os.path.exists(path) and os.path.isfile(path):
|
227
|
+
safe_print(color(AnsiFore.RED, f'The file "{path}" already exists.'))
|
228
|
+
return False
|
229
|
+
|
230
|
+
write_file(path, file_text)
|
218
231
|
storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
|
219
232
|
storage_path = ext_storage_path(os.path.basename(path))
|
220
233
|
storage.save(storage_path)
|