esphome 2025.4.2__py3-none-any.whl → 2025.5.0b3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +16 -14
- esphome/components/ac_dimmer/ac_dimmer.cpp +3 -2
- esphome/components/adc/__init__.py +51 -34
- esphome/components/airthings_wave_base/__init__.py +1 -1
- esphome/components/alarm_control_panel/__init__.py +37 -2
- esphome/components/am43/cover/__init__.py +4 -5
- esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +6 -4
- esphome/components/analog_threshold/analog_threshold_binary_sensor.h +4 -5
- esphome/components/analog_threshold/binary_sensor.py +10 -8
- esphome/components/anova/climate.py +4 -5
- esphome/components/api/__init__.py +25 -8
- esphome/components/api/api_connection.cpp +81 -13
- esphome/components/api/api_connection.h +13 -1
- esphome/components/api/api_frame_helper.cpp +232 -177
- esphome/components/api/api_frame_helper.h +61 -8
- esphome/components/api/api_noise_context.h +13 -4
- esphome/components/api/api_pb2.cpp +1422 -1
- esphome/components/api/api_pb2.h +255 -1
- esphome/components/api/api_pb2_service.cpp +162 -49
- esphome/components/api/api_pb2_service.h +90 -51
- esphome/components/api/api_pb2_size.h +361 -0
- esphome/components/api/api_server.cpp +110 -34
- esphome/components/api/api_server.h +8 -0
- esphome/components/api/proto.h +86 -17
- esphome/components/as7341/as7341.h +1 -1
- esphome/components/atm90e32/__init__.py +1 -0
- esphome/components/atm90e32/atm90e32.cpp +576 -199
- esphome/components/atm90e32/atm90e32.h +128 -31
- esphome/components/atm90e32/atm90e32_reg.h +4 -2
- esphome/components/atm90e32/button/__init__.py +62 -10
- esphome/components/atm90e32/button/atm90e32_button.cpp +63 -4
- esphome/components/atm90e32/button/atm90e32_button.h +36 -4
- esphome/components/atm90e32/number/__init__.py +130 -0
- esphome/components/atm90e32/number/atm90e32_number.h +16 -0
- esphome/components/atm90e32/sensor.py +21 -4
- esphome/components/atm90e32/text_sensor/__init__.py +48 -0
- esphome/components/audio/__init__.py +96 -49
- esphome/components/audio/audio.h +48 -0
- esphome/components/audio/audio_decoder.cpp +1 -1
- esphome/components/audio/audio_resampler.cpp +2 -0
- esphome/components/audio/audio_resampler.h +1 -0
- esphome/components/ballu/climate.py +2 -9
- esphome/components/bang_bang/climate.py +5 -6
- esphome/components/bedjet/bedjet_hub.cpp +1 -0
- esphome/components/bedjet/climate/__init__.py +3 -8
- esphome/components/bedjet/fan/__init__.py +2 -11
- esphome/components/binary/fan/__init__.py +13 -16
- esphome/components/binary_sensor/__init__.py +13 -10
- esphome/components/bl0906/constants.h +16 -16
- esphome/components/ble_client/text_sensor/__init__.py +3 -5
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +4 -6
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +136 -21
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +7 -0
- esphome/components/button/__init__.py +11 -8
- esphome/components/canbus/canbus.cpp +3 -0
- esphome/components/canbus/canbus.h +16 -0
- esphome/components/ccs811/sensor.py +9 -6
- esphome/components/climate/__init__.py +35 -2
- esphome/components/climate/climate_mode.h +1 -1
- esphome/components/climate/climate_traits.h +63 -57
- esphome/components/climate_ir/__init__.py +57 -17
- esphome/components/climate_ir_lg/climate.py +2 -5
- esphome/components/climate_ir_lg/climate_ir_lg.cpp +7 -7
- esphome/components/climate_ir_lg/climate_ir_lg.h +1 -1
- esphome/components/color/__init__.py +2 -0
- esphome/components/const/__init__.py +5 -0
- esphome/components/coolix/climate.py +2 -9
- esphome/components/copy/cover/__init__.py +10 -9
- esphome/components/copy/fan/__init__.py +11 -9
- esphome/components/copy/lock/__init__.py +11 -9
- esphome/components/copy/text/__init__.py +9 -6
- esphome/components/cover/__init__.py +37 -2
- esphome/components/cse7766/cse7766.cpp +2 -1
- esphome/components/cst226/binary_sensor/__init__.py +28 -0
- esphome/components/cst226/binary_sensor/cs226_button.h +22 -0
- esphome/components/cst226/binary_sensor/cstt6_button.cpp +19 -0
- esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +27 -5
- esphome/components/cst226/touchscreen/cst226_touchscreen.h +10 -10
- esphome/components/current_based/cover.py +37 -36
- esphome/components/current_based/current_based_cover.cpp +2 -1
- esphome/components/daikin/climate.py +2 -9
- esphome/components/daikin/daikin.cpp +15 -9
- esphome/components/daikin/daikin.h +5 -5
- esphome/components/daikin_arc/climate.py +2 -7
- esphome/components/daikin_brc/climate.py +3 -5
- esphome/components/dallas_temp/dallas_temp.cpp +17 -24
- esphome/components/dallas_temp/dallas_temp.h +0 -1
- esphome/components/daly_bms/daly_bms.cpp +2 -1
- esphome/components/debug/debug_component.cpp +6 -1
- esphome/components/debug/debug_component.h +6 -0
- esphome/components/debug/debug_esp32.cpp +109 -254
- esphome/components/debug/sensor.py +14 -0
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +13 -1
- esphome/components/delonghi/climate.py +2 -9
- esphome/components/demo/__init__.py +18 -20
- esphome/components/dfrobot_sen0395/switch/__init__.py +21 -22
- esphome/components/dps310/sensor.py +6 -6
- esphome/components/ee895/sensor.py +9 -9
- esphome/components/emmeti/climate.py +2 -9
- esphome/components/endstop/cover.py +17 -16
- esphome/components/endstop/endstop_cover.cpp +2 -1
- esphome/components/ens160_base/__init__.py +12 -9
- esphome/components/esp32/__init__.py +60 -3
- esphome/components/esp32/core.cpp +11 -5
- esphome/components/esp32/gpio.cpp +86 -24
- esphome/components/esp32/gpio.py +15 -16
- esphome/components/esp32/gpio_esp32.py +1 -2
- esphome/components/esp32/gpio_esp32_c2.py +1 -1
- esphome/components/esp32/gpio_esp32_c3.py +1 -1
- esphome/components/esp32/gpio_esp32_c6.py +1 -1
- esphome/components/esp32/gpio_esp32_h2.py +1 -1
- esphome/components/esp32_ble/ble.cpp +1 -0
- esphome/components/esp32_ble/ble.h +5 -3
- esphome/components/esp32_ble/ble_advertising.cpp +2 -1
- esphome/components/esp32_ble/ble_advertising.h +1 -0
- esphome/components/esp32_ble_server/__init__.py +3 -0
- esphome/components/esp32_ble_tracker/__init__.py +7 -1
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +192 -118
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +29 -3
- esphome/components/esp32_camera/esp32_camera.cpp +2 -1
- esphome/components/esp32_camera/esp32_camera.h +1 -1
- esphome/components/esp32_can/esp32_can.cpp +1 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
- esphome/components/esp32_rmt_led_strip/led_strip.h +7 -5
- esphome/components/esp32_rmt_led_strip/light.py +9 -1
- esphome/components/esp32_touch/esp32_touch.cpp +1 -1
- esphome/components/esp8266/gpio.cpp +69 -8
- esphome/components/ethernet/ethernet_component.cpp +1 -1
- esphome/components/event/__init__.py +13 -10
- esphome/components/factory_reset/switch/__init__.py +7 -21
- esphome/components/fan/__init__.py +52 -5
- esphome/components/fastled_base/__init__.py +1 -4
- esphome/components/fastled_base/fastled_light.cpp +1 -1
- esphome/components/feedback/cover.py +38 -33
- esphome/components/feedback/feedback_cover.cpp +2 -1
- esphome/components/fujitsu_general/climate.py +2 -9
- esphome/components/gcja5/gcja5.cpp +2 -1
- esphome/components/gpio/one_wire/gpio_one_wire.cpp +45 -43
- esphome/components/gpio/one_wire/gpio_one_wire.h +2 -1
- esphome/components/gpio_expander/cached_gpio.h +22 -7
- esphome/components/gps/__init__.py +47 -17
- esphome/components/gps/gps.cpp +42 -23
- esphome/components/gps/gps.h +17 -13
- esphome/components/graph/__init__.py +1 -2
- esphome/components/gree/climate.py +4 -6
- esphome/components/gree/gree.cpp +16 -2
- esphome/components/gree/gree.h +2 -2
- esphome/components/growatt_solar/growatt_solar.cpp +2 -1
- esphome/components/haier/climate.py +37 -34
- esphome/components/hbridge/fan/__init__.py +19 -17
- esphome/components/he60r/cover.py +4 -5
- esphome/components/heatpumpir/climate.py +3 -6
- esphome/components/hitachi_ac344/climate.py +2 -9
- esphome/components/hitachi_ac424/climate.py +2 -9
- esphome/components/hm3301/hm3301.h +1 -1
- esphome/components/hte501/sensor.py +6 -6
- esphome/components/http_request/__init__.py +39 -6
- esphome/components/http_request/http_request.cpp +20 -0
- esphome/components/http_request/http_request.h +57 -15
- esphome/components/http_request/http_request_arduino.cpp +22 -6
- esphome/components/http_request/http_request_arduino.h +4 -3
- esphome/components/http_request/http_request_host.cpp +141 -0
- esphome/components/http_request/http_request_host.h +37 -0
- esphome/components/http_request/http_request_idf.cpp +35 -3
- esphome/components/http_request/http_request_idf.h +10 -3
- esphome/components/http_request/httplib.h +9691 -0
- esphome/components/http_request/update/__init__.py +11 -8
- esphome/components/hyt271/sensor.py +6 -6
- esphome/components/i2c/i2c.h +4 -0
- esphome/components/i2c/i2c_bus_esp_idf.cpp +1 -1
- esphome/components/i2s_audio/__init__.py +131 -22
- esphome/components/i2s_audio/i2s_audio.h +44 -4
- esphome/components/i2s_audio/media_player/__init__.py +19 -9
- esphome/components/i2s_audio/microphone/__init__.py +63 -5
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +351 -61
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +40 -6
- esphome/components/i2s_audio/speaker/__init__.py +31 -5
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +155 -19
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +17 -4
- esphome/components/ili9xxx/ili9xxx_init.h +1 -1
- esphome/components/image/__init__.py +37 -17
- esphome/components/image/image.cpp +25 -8
- esphome/components/internal_temperature/internal_temperature.cpp +6 -4
- esphome/components/key_collector/__init__.py +35 -0
- esphome/components/key_collector/key_collector.cpp +8 -0
- esphome/components/key_collector/key_collector.h +10 -0
- esphome/components/kuntze/kuntze.cpp +2 -1
- esphome/components/ld2410/ld2410.h +1 -1
- esphome/components/ld2450/ld2450.h +1 -1
- esphome/components/light/__init__.py +57 -0
- esphome/components/lock/__init__.py +51 -4
- esphome/components/lock/automation.h +2 -13
- esphome/components/logger/__init__.py +22 -0
- esphome/components/logger/logger.cpp +154 -103
- esphome/components/logger/logger.h +211 -36
- esphome/components/logger/task_log_buffer.cpp +138 -0
- esphome/components/logger/task_log_buffer.h +69 -0
- esphome/components/lvgl/__init__.py +13 -5
- esphome/components/lvgl/automation.py +50 -1
- esphome/components/lvgl/defines.py +0 -1
- esphome/components/lvgl/lvgl_esphome.cpp +5 -1
- esphome/components/lvgl/text/__init__.py +1 -2
- esphome/components/mapping/__init__.py +134 -0
- esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
- esphome/components/max7219digit/max7219digit.cpp +28 -27
- esphome/components/mdns/__init__.py +11 -5
- esphome/components/mdns/mdns_component.cpp +11 -5
- esphome/components/mdns/mdns_component.h +3 -2
- esphome/components/mdns/mdns_esp32.cpp +4 -3
- esphome/components/mdns/mdns_esp8266.cpp +4 -2
- esphome/components/mdns/mdns_libretiny.cpp +4 -2
- esphome/components/mdns/mdns_rp2040.cpp +4 -2
- esphome/components/media_player/__init__.py +33 -1
- esphome/components/mhz19/sensor.py +11 -7
- esphome/components/micro_wake_word/__init__.py +99 -31
- esphome/components/micro_wake_word/automation.h +54 -0
- esphome/components/micro_wake_word/micro_wake_word.cpp +331 -319
- esphome/components/micro_wake_word/micro_wake_word.h +58 -105
- esphome/components/micro_wake_word/preprocessor_settings.h +19 -2
- esphome/components/micro_wake_word/streaming_model.cpp +158 -41
- esphome/components/micro_wake_word/streaming_model.h +85 -13
- esphome/components/microphone/__init__.py +139 -9
- esphome/components/microphone/automation.h +14 -2
- esphome/components/microphone/microphone.cpp +21 -0
- esphome/components/microphone/microphone.h +14 -5
- esphome/components/microphone/microphone_source.cpp +95 -0
- esphome/components/microphone/microphone_source.h +80 -0
- esphome/components/mics_4514/sensor.py +25 -14
- esphome/components/midea/climate.py +3 -4
- esphome/components/midea_ir/climate.py +3 -5
- esphome/components/mipi_spi/__init__.py +15 -0
- esphome/components/mipi_spi/display.py +474 -0
- esphome/components/mipi_spi/mipi_spi.cpp +481 -0
- esphome/components/mipi_spi/mipi_spi.h +171 -0
- esphome/components/mipi_spi/models/__init__.py +65 -0
- esphome/components/mipi_spi/models/amoled.py +72 -0
- esphome/components/mipi_spi/models/commands.py +82 -0
- esphome/components/mipi_spi/models/cyd.py +10 -0
- esphome/components/mipi_spi/models/ili.py +749 -0
- esphome/components/mipi_spi/models/jc.py +260 -0
- esphome/components/mipi_spi/models/lanbon.py +15 -0
- esphome/components/mipi_spi/models/lilygo.py +60 -0
- esphome/components/mipi_spi/models/waveshare.py +139 -0
- esphome/components/mitsubishi/climate.py +2 -5
- esphome/components/mitsubishi/mitsubishi.cpp +9 -9
- esphome/components/mixer/speaker/mixer_speaker.cpp +12 -22
- esphome/components/mixer/speaker/mixer_speaker.h +1 -3
- esphome/components/mlx90393/sensor.py +5 -0
- esphome/components/mlx90393/sensor_mlx90393.cpp +195 -13
- esphome/components/mlx90393/sensor_mlx90393.h +21 -4
- esphome/components/modbus/modbus.cpp +2 -1
- esphome/components/mqtt/__init__.py +1 -1
- esphome/components/mqtt/mqtt_client.cpp +6 -2
- esphome/components/mqtt/mqtt_const.h +4 -0
- esphome/components/mqtt/mqtt_fan.cpp +39 -0
- esphome/components/mqtt/mqtt_fan.h +2 -0
- esphome/components/ms5611/sensor.py +6 -6
- esphome/components/ms8607/sensor.py +3 -3
- esphome/components/network/__init__.py +1 -1
- esphome/components/nextion/base_component.py +17 -16
- esphome/components/nextion/display.py +11 -2
- esphome/components/nextion/nextion.cpp +39 -1
- esphome/components/nextion/nextion.h +50 -0
- esphome/components/noblex/climate.py +2 -9
- esphome/components/number/__init__.py +12 -9
- esphome/components/one_wire/one_wire_bus.cpp +14 -10
- esphome/components/one_wire/one_wire_bus.h +14 -8
- esphome/components/online_image/bmp_image.cpp +48 -11
- esphome/components/online_image/bmp_image.h +2 -0
- esphome/components/opentherm/binary_sensor/__init__.py +2 -4
- esphome/components/opentherm/number/__init__.py +11 -20
- esphome/components/opentherm/sensor/__init__.py +3 -3
- esphome/components/opentherm/switch/__init__.py +3 -5
- esphome/components/output/lock/__init__.py +11 -9
- esphome/components/packages/__init__.py +33 -31
- esphome/components/packet_transport/__init__.py +201 -0
- esphome/components/packet_transport/binary_sensor.py +19 -0
- esphome/components/packet_transport/packet_transport.cpp +534 -0
- esphome/components/packet_transport/packet_transport.h +154 -0
- esphome/components/packet_transport/sensor.py +19 -0
- esphome/components/pca9685/pca9685_output.cpp +2 -1
- esphome/components/pid/climate.py +2 -4
- esphome/components/pm2005/__init__.py +1 -0
- esphome/components/pm2005/pm2005.cpp +123 -0
- esphome/components/pm2005/pm2005.h +46 -0
- esphome/components/pm2005/sensor.py +86 -0
- esphome/components/pmsa003i/pmsa003i.cpp +43 -16
- esphome/components/pmsa003i/pmsa003i.h +25 -25
- esphome/components/pmsx003/pmsx003.cpp +195 -230
- esphome/components/pmsx003/pmsx003.h +51 -33
- esphome/components/pmsx003/sensor.py +21 -11
- esphome/components/pn7150/pn7150.h +2 -2
- esphome/components/pn7160/pn7160.h +2 -2
- esphome/components/prometheus/prometheus_handler.cpp +174 -0
- esphome/components/prometheus/prometheus_handler.h +17 -0
- esphome/components/psram/__init__.py +7 -5
- esphome/components/pulse_meter/pulse_meter_sensor.cpp +32 -12
- esphome/components/pulse_meter/pulse_meter_sensor.h +5 -5
- esphome/components/pzem004t/pzem004t.cpp +2 -1
- esphome/components/qspi_dbi/__init__.py +0 -1
- esphome/components/qspi_dbi/display.py +2 -1
- esphome/components/qspi_dbi/models.py +1 -2
- esphome/components/remote_base/__init__.py +91 -0
- esphome/components/remote_base/beo4_protocol.cpp +153 -0
- esphome/components/remote_base/beo4_protocol.h +43 -0
- esphome/components/remote_base/gobox_protocol.cpp +131 -0
- esphome/components/remote_base/gobox_protocol.h +54 -0
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +16 -9
- esphome/components/resampler/speaker/resampler_speaker.cpp +12 -10
- esphome/components/resampler/speaker/resampler_speaker.h +1 -1
- esphome/components/rf_bridge/rf_bridge.cpp +2 -1
- esphome/components/scd30/sensor.py +2 -3
- esphome/components/scd4x/sensor.py +4 -5
- esphome/components/sdp3x/sensor.py +2 -1
- esphome/components/sds011/sds011.cpp +2 -1
- esphome/components/select/__init__.py +19 -20
- esphome/components/sen5x/sen5x.cpp +55 -36
- esphome/components/sen5x/sensor.py +1 -1
- esphome/components/senseair/sensor.py +3 -3
- esphome/components/sensor/__init__.py +158 -14
- esphome/components/sensor/filter.cpp +23 -0
- esphome/components/sensor/filter.h +22 -0
- esphome/components/sgp30/sensor.py +14 -16
- esphome/components/sgp4x/sensor.py +1 -1
- esphome/components/sht4x/sht4x.cpp +43 -22
- esphome/components/sht4x/sht4x.h +1 -1
- esphome/components/shtcx/sensor.py +6 -6
- esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
- esphome/components/sml/text_sensor/__init__.py +4 -6
- esphome/components/sound_level/__init__.py +0 -0
- esphome/components/sound_level/sensor.py +97 -0
- esphome/components/sound_level/sound_level.cpp +194 -0
- esphome/components/sound_level/sound_level.h +73 -0
- esphome/components/speaker/media_player/__init__.py +4 -8
- esphome/components/speaker/media_player/speaker_media_player.cpp +0 -18
- esphome/components/speaker/media_player/speaker_media_player.h +0 -11
- esphome/components/speaker/speaker.h +4 -7
- esphome/components/speed/fan/__init__.py +17 -16
- esphome/components/spi/spi.h +11 -1
- esphome/components/sprinkler/__init__.py +18 -19
- esphome/components/sprinkler/sprinkler.cpp +6 -5
- esphome/components/switch/__init__.py +32 -42
- esphome/components/syslog/__init__.py +41 -0
- esphome/components/syslog/esphome_syslog.cpp +49 -0
- esphome/components/syslog/esphome_syslog.h +27 -0
- esphome/components/t6615/sensor.py +3 -3
- esphome/components/t6615/t6615.cpp +2 -1
- esphome/components/tca9555/tca9555.cpp +11 -6
- esphome/components/tcl112/climate.py +2 -9
- esphome/components/template/alarm_control_panel/__init__.py +7 -6
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +21 -17
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +2 -1
- esphome/components/template/cover/__init__.py +27 -21
- esphome/components/template/fan/__init__.py +14 -12
- esphome/components/template/lock/__init__.py +20 -25
- esphome/components/template/lock/automation.h +18 -0
- esphome/components/template/text/__init__.py +4 -3
- esphome/components/template/valve/__init__.py +32 -21
- esphome/components/template/valve/automation.h +24 -0
- esphome/components/text/__init__.py +32 -1
- esphome/components/text_sensor/__init__.py +24 -29
- esphome/components/thermostat/climate.py +5 -5
- esphome/components/time_based/cover.py +17 -16
- esphome/components/time_based/time_based_cover.cpp +2 -1
- esphome/components/tm1638/switch/__init__.py +10 -7
- esphome/components/tormatic/cover.py +4 -5
- esphome/components/toshiba/climate.py +3 -5
- esphome/components/touchscreen/touchscreen.cpp +3 -1
- esphome/components/tuya/climate/__init__.py +5 -6
- esphome/components/tuya/cover/__init__.py +6 -11
- esphome/components/tuya/select/__init__.py +15 -5
- esphome/components/tuya/select/tuya_select.cpp +6 -1
- esphome/components/tuya/select/tuya_select.h +5 -1
- esphome/components/uart/packet_transport/__init__.py +20 -0
- esphome/components/uart/packet_transport/uart_transport.cpp +88 -0
- esphome/components/uart/packet_transport/uart_transport.h +41 -0
- esphome/components/uart/switch/uart_switch.cpp +2 -1
- esphome/components/udp/__init__.py +126 -128
- esphome/components/udp/automation.h +40 -0
- esphome/components/udp/binary_sensor.py +3 -25
- esphome/components/udp/packet_transport/__init__.py +29 -0
- esphome/components/udp/packet_transport/udp_transport.cpp +36 -0
- esphome/components/udp/packet_transport/udp_transport.h +28 -0
- esphome/components/udp/sensor.py +3 -25
- esphome/components/udp/udp_component.cpp +26 -470
- esphome/components/udp/udp_component.h +21 -128
- esphome/components/update/__init__.py +31 -1
- esphome/components/uponor_smatrix/climate/__init__.py +4 -9
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
- esphome/components/uptime/text_sensor/__init__.py +47 -7
- esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +12 -7
- esphome/components/uptime/text_sensor/uptime_text_sensor.h +19 -0
- esphome/components/valve/__init__.py +34 -3
- esphome/components/valve/automation.h +1 -19
- esphome/components/vl53l0x/sensor.py +11 -0
- esphome/components/vl53l0x/vl53l0x_sensor.cpp +5 -1
- esphome/components/vl53l0x/vl53l0x_sensor.h +2 -1
- esphome/components/voice_assistant/__init__.py +36 -10
- esphome/components/voice_assistant/voice_assistant.cpp +170 -144
- esphome/components/voice_assistant/voice_assistant.h +26 -25
- esphome/components/waveshare_epaper/display.py +6 -0
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +439 -37
- esphome/components/waveshare_epaper/waveshare_epaper.h +60 -11
- esphome/components/whirlpool/climate.py +3 -5
- esphome/components/whynter/climate.py +3 -5
- esphome/components/xpt2046/touchscreen/xpt2046.cpp +1 -1
- esphome/components/yashima/climate.py +6 -6
- esphome/components/zhlt01/climate.py +2 -7
- esphome/config.py +13 -13
- esphome/config_validation.py +38 -58
- esphome/const.py +15 -1
- esphome/core/__init__.py +2 -0
- esphome/core/application.cpp +23 -10
- esphome/core/application.h +9 -1
- esphome/core/automation.h +4 -3
- esphome/core/component.cpp +28 -7
- esphome/core/component.h +10 -1
- esphome/core/defines.h +23 -17
- esphome/core/macros.h +4 -0
- esphome/core/scheduler.cpp +7 -1
- esphome/cpp_generator.py +6 -2
- esphome/dashboard/web_server.py +3 -3
- esphome/helpers.py +39 -0
- esphome/loader.py +4 -0
- esphome/log.py +15 -19
- esphome/mqtt.py +23 -10
- esphome/platformio_api.py +1 -1
- esphome/schema_extractors.py +0 -1
- esphome/voluptuous_schema.py +3 -1
- esphome/vscode.py +15 -0
- esphome/wizard.py +47 -37
- esphome/zeroconf.py +7 -3
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +10 -11
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +440 -380
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +1 -1
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.4.2.dist-info → esphome-2025.5.0b3.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@
|
|
5
5
|
#include "esphome/core/helpers.h"
|
6
6
|
#include "esphome/core/application.h"
|
7
7
|
#include "proto.h"
|
8
|
+
#include "api_pb2_size.h"
|
8
9
|
#include <cstring>
|
9
10
|
|
10
11
|
namespace esphome {
|
@@ -72,6 +73,91 @@ const char *api_error_to_str(APIError err) {
|
|
72
73
|
return "UNKNOWN";
|
73
74
|
}
|
74
75
|
|
76
|
+
// Common implementation for writing raw data to socket
|
77
|
+
template<typename StateEnum>
|
78
|
+
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
|
79
|
+
std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
|
80
|
+
StateEnum failed_state) {
|
81
|
+
// This method writes data to socket or buffers it
|
82
|
+
// Returns APIError::OK if successful (or would block, but data has been buffered)
|
83
|
+
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
|
84
|
+
|
85
|
+
if (iovcnt == 0)
|
86
|
+
return APIError::OK; // Nothing to do, success
|
87
|
+
|
88
|
+
size_t total_write_len = 0;
|
89
|
+
for (int i = 0; i < iovcnt; i++) {
|
90
|
+
#ifdef HELPER_LOG_PACKETS
|
91
|
+
ESP_LOGVV(TAG, "Sending raw: %s",
|
92
|
+
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
93
|
+
#endif
|
94
|
+
total_write_len += iov[i].iov_len;
|
95
|
+
}
|
96
|
+
|
97
|
+
if (!tx_buf.empty()) {
|
98
|
+
// try to empty tx_buf first
|
99
|
+
while (!tx_buf.empty()) {
|
100
|
+
ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
|
101
|
+
if (is_would_block(sent)) {
|
102
|
+
break;
|
103
|
+
} else if (sent == -1) {
|
104
|
+
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
|
105
|
+
state = failed_state;
|
106
|
+
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
107
|
+
}
|
108
|
+
// TODO: inefficient if multiple packets in txbuf
|
109
|
+
// replace with deque of buffers
|
110
|
+
tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
if (!tx_buf.empty()) {
|
115
|
+
// tx buf not empty, can't write now because then stream would be inconsistent
|
116
|
+
// Reserve space upfront to avoid multiple reallocations
|
117
|
+
tx_buf.reserve(tx_buf.size() + total_write_len);
|
118
|
+
for (int i = 0; i < iovcnt; i++) {
|
119
|
+
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
120
|
+
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
121
|
+
}
|
122
|
+
return APIError::OK; // Success, data buffered
|
123
|
+
}
|
124
|
+
|
125
|
+
ssize_t sent = socket->writev(iov, iovcnt);
|
126
|
+
if (is_would_block(sent)) {
|
127
|
+
// operation would block, add buffer to tx_buf
|
128
|
+
// Reserve space upfront to avoid multiple reallocations
|
129
|
+
tx_buf.reserve(tx_buf.size() + total_write_len);
|
130
|
+
for (int i = 0; i < iovcnt; i++) {
|
131
|
+
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
132
|
+
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
133
|
+
}
|
134
|
+
return APIError::OK; // Success, data buffered
|
135
|
+
} else if (sent == -1) {
|
136
|
+
// an error occurred
|
137
|
+
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
|
138
|
+
state = failed_state;
|
139
|
+
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
140
|
+
} else if ((size_t) sent != total_write_len) {
|
141
|
+
// partially sent, add end to tx_buf
|
142
|
+
size_t remaining = total_write_len - sent;
|
143
|
+
// Reserve space upfront to avoid multiple reallocations
|
144
|
+
tx_buf.reserve(tx_buf.size() + remaining);
|
145
|
+
|
146
|
+
size_t to_consume = sent;
|
147
|
+
for (int i = 0; i < iovcnt; i++) {
|
148
|
+
if (to_consume >= iov[i].iov_len) {
|
149
|
+
to_consume -= iov[i].iov_len;
|
150
|
+
} else {
|
151
|
+
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
152
|
+
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
153
|
+
to_consume = 0;
|
154
|
+
}
|
155
|
+
}
|
156
|
+
return APIError::OK; // Success, data buffered
|
157
|
+
}
|
158
|
+
return APIError::OK; // Success, all data sent
|
159
|
+
}
|
160
|
+
|
75
161
|
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
|
76
162
|
// uncomment to log raw packets
|
77
163
|
//#define HELPER_LOG_PACKETS
|
@@ -407,9 +493,12 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
|
|
407
493
|
std::vector<uint8_t> data;
|
408
494
|
data.resize(reason.length() + 1);
|
409
495
|
data[0] = 0x01; // failure
|
410
|
-
|
411
|
-
|
496
|
+
|
497
|
+
// Copy error message in bulk
|
498
|
+
if (!reason.empty()) {
|
499
|
+
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
412
500
|
}
|
501
|
+
|
413
502
|
// temporarily remove failed state
|
414
503
|
auto orig_state = state_;
|
415
504
|
state_ = State::EXPLICIT_REJECT;
|
@@ -471,7 +560,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|
471
560
|
return APIError::OK;
|
472
561
|
}
|
473
562
|
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
474
|
-
APIError APINoiseFrameHelper::
|
563
|
+
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
475
564
|
int err;
|
476
565
|
APIError aerr;
|
477
566
|
aerr = state_action_();
|
@@ -483,31 +572,36 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
|
483
572
|
return APIError::WOULD_BLOCK;
|
484
573
|
}
|
485
574
|
|
575
|
+
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
576
|
+
// Message data starts after padding
|
577
|
+
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
486
578
|
size_t padding = 0;
|
487
579
|
size_t msg_len = 4 + payload_len + padding;
|
488
|
-
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
|
489
|
-
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
|
490
|
-
if (tmpbuf == nullptr) {
|
491
|
-
HELPER_LOG("Could not allocate for writing packet");
|
492
|
-
return APIError::OUT_OF_MEMORY;
|
493
|
-
}
|
494
580
|
|
495
|
-
|
496
|
-
|
581
|
+
// We need to resize to include MAC space, but we already reserved it in create_buffer
|
582
|
+
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
583
|
+
|
584
|
+
// Write the noise header in the padded area
|
585
|
+
// Buffer layout:
|
586
|
+
// [0] - 0x01 indicator byte
|
587
|
+
// [1-2] - Size of encrypted payload (filled after encryption)
|
588
|
+
// [3-4] - Message type (encrypted)
|
589
|
+
// [5-6] - Payload length (encrypted)
|
590
|
+
// [7...] - Actual payload data (encrypted)
|
591
|
+
uint8_t *buf_start = raw_buffer->data();
|
592
|
+
buf_start[0] = 0x01; // indicator
|
593
|
+
// buf_start[1], buf_start[2] to be set later after encryption
|
497
594
|
const uint8_t msg_offset = 3;
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
// copy data
|
504
|
-
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
505
|
-
// fill padding with zeros
|
506
|
-
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
|
595
|
+
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
596
|
+
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
597
|
+
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
598
|
+
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
599
|
+
// payload data is already in the buffer starting at position 7
|
507
600
|
|
508
601
|
NoiseBuffer mbuf;
|
509
602
|
noise_buffer_init(mbuf);
|
510
|
-
|
603
|
+
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
|
604
|
+
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
511
605
|
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
512
606
|
if (err != 0) {
|
513
607
|
state_ = State::FAILED;
|
@@ -516,11 +610,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
|
516
610
|
}
|
517
611
|
|
518
612
|
size_t total_len = 3 + mbuf.size;
|
519
|
-
|
520
|
-
|
613
|
+
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
614
|
+
buf_start[2] = (uint8_t) mbuf.size;
|
521
615
|
|
522
616
|
struct iovec iov;
|
523
|
-
|
617
|
+
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
|
618
|
+
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
|
619
|
+
iov.iov_base = buf_start;
|
524
620
|
iov.iov_len = total_len;
|
525
621
|
|
526
622
|
// write raw to not have two packets sent if NAGLE disabled
|
@@ -546,71 +642,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
|
|
546
642
|
|
547
643
|
return APIError::OK;
|
548
644
|
}
|
549
|
-
/** Write the data to the socket, or buffer it a write would block
|
550
|
-
*
|
551
|
-
* @param data The data to write
|
552
|
-
* @param len The length of data
|
553
|
-
*/
|
554
|
-
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
555
|
-
if (iovcnt == 0)
|
556
|
-
return APIError::OK;
|
557
|
-
APIError aerr;
|
558
|
-
|
559
|
-
size_t total_write_len = 0;
|
560
|
-
for (int i = 0; i < iovcnt; i++) {
|
561
|
-
#ifdef HELPER_LOG_PACKETS
|
562
|
-
ESP_LOGVV(TAG, "Sending raw: %s",
|
563
|
-
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
564
|
-
#endif
|
565
|
-
total_write_len += iov[i].iov_len;
|
566
|
-
}
|
567
|
-
|
568
|
-
if (!tx_buf_.empty()) {
|
569
|
-
// try to empty tx_buf_ first
|
570
|
-
aerr = try_send_tx_buf_();
|
571
|
-
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
|
572
|
-
return aerr;
|
573
|
-
}
|
574
|
-
|
575
|
-
if (!tx_buf_.empty()) {
|
576
|
-
// tx buf not empty, can't write now because then stream would be inconsistent
|
577
|
-
for (int i = 0; i < iovcnt; i++) {
|
578
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
579
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
580
|
-
}
|
581
|
-
return APIError::OK;
|
582
|
-
}
|
583
|
-
|
584
|
-
ssize_t sent = socket_->writev(iov, iovcnt);
|
585
|
-
if (is_would_block(sent)) {
|
586
|
-
// operation would block, add buffer to tx_buf
|
587
|
-
for (int i = 0; i < iovcnt; i++) {
|
588
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
589
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
590
|
-
}
|
591
|
-
return APIError::OK;
|
592
|
-
} else if (sent == -1) {
|
593
|
-
// an error occurred
|
594
|
-
state_ = State::FAILED;
|
595
|
-
HELPER_LOG("Socket write failed with errno %d", errno);
|
596
|
-
return APIError::SOCKET_WRITE_FAILED;
|
597
|
-
} else if ((size_t) sent != total_write_len) {
|
598
|
-
// partially sent, add end to tx_buf
|
599
|
-
size_t to_consume = sent;
|
600
|
-
for (int i = 0; i < iovcnt; i++) {
|
601
|
-
if (to_consume >= iov[i].iov_len) {
|
602
|
-
to_consume -= iov[i].iov_len;
|
603
|
-
} else {
|
604
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
605
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
606
|
-
to_consume = 0;
|
607
|
-
}
|
608
|
-
}
|
609
|
-
return APIError::OK;
|
610
|
-
}
|
611
|
-
// fully sent
|
612
|
-
return APIError::OK;
|
613
|
-
}
|
614
645
|
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
615
646
|
uint8_t header[3];
|
616
647
|
header[0] = 0x01; // indicator
|
@@ -697,6 +728,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
|
697
728
|
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
698
729
|
}
|
699
730
|
|
731
|
+
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
732
|
+
|
700
733
|
HELPER_LOG("Handshake complete!");
|
701
734
|
noise_handshakestate_free(handshake_);
|
702
735
|
handshake_ = nullptr;
|
@@ -744,6 +777,11 @@ void noise_rand_bytes(void *output, size_t len) {
|
|
744
777
|
}
|
745
778
|
}
|
746
779
|
}
|
780
|
+
|
781
|
+
// Explicit template instantiation for Noise
|
782
|
+
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
|
783
|
+
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
|
784
|
+
APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
|
747
785
|
#endif // USE_API_NOISE
|
748
786
|
|
749
787
|
#ifdef USE_API_PLAINTEXT
|
@@ -804,6 +842,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|
804
842
|
// read header
|
805
843
|
while (!rx_header_parsed_) {
|
806
844
|
uint8_t data;
|
845
|
+
// Reading one byte at a time is fastest in practice for ESP32 when
|
846
|
+
// there is no data on the wire (which is the common case).
|
847
|
+
// This results in faster failure detection compared to
|
848
|
+
// attempting to read multiple bytes at once.
|
807
849
|
ssize_t received = socket_->read(&data, 1);
|
808
850
|
if (received == -1) {
|
809
851
|
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
@@ -817,27 +859,60 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|
817
859
|
HELPER_LOG("Connection closed");
|
818
860
|
return APIError::CONNECTION_CLOSED;
|
819
861
|
}
|
820
|
-
rx_header_buf_.push_back(data);
|
821
862
|
|
822
|
-
//
|
823
|
-
|
863
|
+
// Successfully read a byte
|
864
|
+
|
865
|
+
// Process byte according to current buffer position
|
866
|
+
if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
|
867
|
+
if (data != 0x00) {
|
868
|
+
state_ = State::FAILED;
|
869
|
+
HELPER_LOG("Bad indicator byte %u", data);
|
870
|
+
return APIError::BAD_INDICATOR;
|
871
|
+
}
|
872
|
+
// We don't store the indicator byte, just increment position
|
873
|
+
rx_header_buf_pos_ = 1; // Set to 1 directly
|
874
|
+
continue; // Need more bytes before we can parse
|
875
|
+
}
|
876
|
+
|
877
|
+
// Check buffer overflow before storing
|
878
|
+
if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
|
824
879
|
state_ = State::FAILED;
|
825
|
-
HELPER_LOG("
|
826
|
-
return APIError::
|
880
|
+
HELPER_LOG("Header buffer overflow");
|
881
|
+
return APIError::BAD_DATA_PACKET;
|
827
882
|
}
|
828
883
|
|
829
|
-
|
884
|
+
// Store byte in buffer (adjust index to account for skipped indicator byte)
|
885
|
+
rx_header_buf_[rx_header_buf_pos_ - 1] = data;
|
886
|
+
|
887
|
+
// Increment position after storing
|
888
|
+
rx_header_buf_pos_++;
|
889
|
+
|
890
|
+
// Case 3: If we only have one varint byte, we need more
|
891
|
+
if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
|
892
|
+
continue; // Need more bytes before we can parse
|
893
|
+
}
|
894
|
+
|
895
|
+
// At this point, we have at least 3 bytes total:
|
896
|
+
// - Validated indicator byte (0x00) but not stored
|
897
|
+
// - At least 2 bytes in the buffer for the varints
|
898
|
+
// Buffer layout:
|
899
|
+
// First 1-3 bytes: Message size varint (variable length)
|
900
|
+
// - 2 bytes would only allow up to 16383, which is less than noise's 65535
|
901
|
+
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
|
902
|
+
// Remaining 1-2 bytes: Message type varint (variable length)
|
903
|
+
// We now attempt to parse both varints. If either is incomplete,
|
904
|
+
// we'll continue reading more bytes.
|
905
|
+
|
830
906
|
uint32_t consumed = 0;
|
831
|
-
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[
|
907
|
+
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
|
832
908
|
if (!msg_size_varint.has_value()) {
|
833
909
|
// not enough data there yet
|
834
910
|
continue;
|
835
911
|
}
|
836
912
|
|
837
|
-
i += consumed;
|
838
913
|
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
839
914
|
|
840
|
-
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[
|
915
|
+
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
|
841
916
|
if (!msg_type_varint.has_value()) {
|
842
917
|
// not enough data there yet
|
843
918
|
continue;
|
@@ -883,7 +958,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|
883
958
|
// consume msg
|
884
959
|
rx_buf_ = {};
|
885
960
|
rx_buf_len_ = 0;
|
886
|
-
|
961
|
+
rx_header_buf_pos_ = 0;
|
887
962
|
rx_header_parsed_ = false;
|
888
963
|
return APIError::OK;
|
889
964
|
}
|
@@ -927,26 +1002,66 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|
927
1002
|
return APIError::OK;
|
928
1003
|
}
|
929
1004
|
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
930
|
-
APIError APIPlaintextFrameHelper::
|
1005
|
+
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
931
1006
|
if (state_ != State::DATA) {
|
932
1007
|
return APIError::BAD_STATE;
|
933
1008
|
}
|
934
1009
|
|
935
|
-
std::vector<uint8_t>
|
936
|
-
|
937
|
-
|
938
|
-
ProtoVarInt(type).encode(header);
|
1010
|
+
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
1011
|
+
// Message data starts after padding (frame_header_padding_ = 6)
|
1012
|
+
size_t payload_len = raw_buffer->size() - frame_header_padding_;
|
939
1013
|
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
1014
|
+
// Calculate varint sizes for header components
|
1015
|
+
size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
1016
|
+
size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
1017
|
+
size_t total_header_len = 1 + size_varint_len + type_varint_len;
|
1018
|
+
|
1019
|
+
if (total_header_len > frame_header_padding_) {
|
1020
|
+
// Header is too large to fit in the padding
|
1021
|
+
return APIError::BAD_ARG;
|
945
1022
|
}
|
946
|
-
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
947
|
-
iov[1].iov_len = payload_len;
|
948
1023
|
|
949
|
-
|
1024
|
+
// Calculate where to start writing the header
|
1025
|
+
// The header starts at the latest possible position to minimize unused padding
|
1026
|
+
//
|
1027
|
+
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
1028
|
+
// [0-2] - Unused padding
|
1029
|
+
// [3] - 0x00 indicator byte
|
1030
|
+
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
1031
|
+
// [5] - Message type varint (1 byte, for types 0-127)
|
1032
|
+
// [6...] - Actual payload data
|
1033
|
+
//
|
1034
|
+
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
1035
|
+
// [0-1] - Unused padding
|
1036
|
+
// [2] - 0x00 indicator byte
|
1037
|
+
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
1038
|
+
// [5] - Message type varint (1 byte, for types 0-127)
|
1039
|
+
// [6...] - Actual payload data
|
1040
|
+
//
|
1041
|
+
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
1042
|
+
// [0] - 0x00 indicator byte
|
1043
|
+
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
1044
|
+
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
1045
|
+
// [6...] - Actual payload data
|
1046
|
+
uint8_t *buf_start = raw_buffer->data();
|
1047
|
+
size_t header_offset = frame_header_padding_ - total_header_len;
|
1048
|
+
|
1049
|
+
// Write the plaintext header
|
1050
|
+
buf_start[header_offset] = 0x00; // indicator
|
1051
|
+
|
1052
|
+
// Encode size varint directly into buffer
|
1053
|
+
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
1054
|
+
|
1055
|
+
// Encode type varint directly into buffer
|
1056
|
+
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
1057
|
+
|
1058
|
+
struct iovec iov;
|
1059
|
+
// Point iov_base to the beginning of our header (skip unused padding)
|
1060
|
+
// This ensures we only send the actual header and payload, not the empty padding bytes
|
1061
|
+
iov.iov_base = buf_start + header_offset;
|
1062
|
+
iov.iov_len = total_header_len + payload_len;
|
1063
|
+
|
1064
|
+
return write_raw_(&iov, 1);
|
950
1065
|
}
|
951
1066
|
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
952
1067
|
// try send from tx_buf
|
@@ -966,71 +1081,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
|
|
966
1081
|
|
967
1082
|
return APIError::OK;
|
968
1083
|
}
|
969
|
-
/** Write the data to the socket, or buffer it a write would block
|
970
|
-
*
|
971
|
-
* @param data The data to write
|
972
|
-
* @param len The length of data
|
973
|
-
*/
|
974
|
-
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
975
|
-
if (iovcnt == 0)
|
976
|
-
return APIError::OK;
|
977
|
-
APIError aerr;
|
978
|
-
|
979
|
-
size_t total_write_len = 0;
|
980
|
-
for (int i = 0; i < iovcnt; i++) {
|
981
|
-
#ifdef HELPER_LOG_PACKETS
|
982
|
-
ESP_LOGVV(TAG, "Sending raw: %s",
|
983
|
-
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
984
|
-
#endif
|
985
|
-
total_write_len += iov[i].iov_len;
|
986
|
-
}
|
987
|
-
|
988
|
-
if (!tx_buf_.empty()) {
|
989
|
-
// try to empty tx_buf_ first
|
990
|
-
aerr = try_send_tx_buf_();
|
991
|
-
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
|
992
|
-
return aerr;
|
993
|
-
}
|
994
|
-
|
995
|
-
if (!tx_buf_.empty()) {
|
996
|
-
// tx buf not empty, can't write now because then stream would be inconsistent
|
997
|
-
for (int i = 0; i < iovcnt; i++) {
|
998
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
999
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
1000
|
-
}
|
1001
|
-
return APIError::OK;
|
1002
|
-
}
|
1003
|
-
|
1004
|
-
ssize_t sent = socket_->writev(iov, iovcnt);
|
1005
|
-
if (is_would_block(sent)) {
|
1006
|
-
// operation would block, add buffer to tx_buf
|
1007
|
-
for (int i = 0; i < iovcnt; i++) {
|
1008
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
1009
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
1010
|
-
}
|
1011
|
-
return APIError::OK;
|
1012
|
-
} else if (sent == -1) {
|
1013
|
-
// an error occurred
|
1014
|
-
state_ = State::FAILED;
|
1015
|
-
HELPER_LOG("Socket write failed with errno %d", errno);
|
1016
|
-
return APIError::SOCKET_WRITE_FAILED;
|
1017
|
-
} else if ((size_t) sent != total_write_len) {
|
1018
|
-
// partially sent, add end to tx_buf
|
1019
|
-
size_t to_consume = sent;
|
1020
|
-
for (int i = 0; i < iovcnt; i++) {
|
1021
|
-
if (to_consume >= iov[i].iov_len) {
|
1022
|
-
to_consume -= iov[i].iov_len;
|
1023
|
-
} else {
|
1024
|
-
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
|
1025
|
-
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
1026
|
-
to_consume = 0;
|
1027
|
-
}
|
1028
|
-
}
|
1029
|
-
return APIError::OK;
|
1030
|
-
}
|
1031
|
-
// fully sent
|
1032
|
-
return APIError::OK;
|
1033
|
-
}
|
1034
1084
|
|
1035
1085
|
APIError APIPlaintextFrameHelper::close() {
|
1036
1086
|
state_ = State::CLOSED;
|
@@ -1048,6 +1098,11 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
|
|
1048
1098
|
}
|
1049
1099
|
return APIError::OK;
|
1050
1100
|
}
|
1101
|
+
|
1102
|
+
// Explicit template instantiation for Plaintext
|
1103
|
+
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
|
1104
|
+
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
|
1105
|
+
APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
|
1051
1106
|
#endif // USE_API_PLAINTEXT
|
1052
1107
|
|
1053
1108
|
} // namespace api
|
@@ -16,6 +16,8 @@
|
|
16
16
|
namespace esphome {
|
17
17
|
namespace api {
|
18
18
|
|
19
|
+
class ProtoWriteBuffer;
|
20
|
+
|
19
21
|
struct ReadPacketBuffer {
|
20
22
|
std::vector<uint8_t> container;
|
21
23
|
uint16_t type;
|
@@ -65,26 +67,46 @@ class APIFrameHelper {
|
|
65
67
|
virtual APIError loop() = 0;
|
66
68
|
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
67
69
|
virtual bool can_write_without_blocking() = 0;
|
68
|
-
virtual APIError
|
70
|
+
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
69
71
|
virtual std::string getpeername() = 0;
|
70
72
|
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
71
73
|
virtual APIError close() = 0;
|
72
74
|
virtual APIError shutdown(int how) = 0;
|
73
75
|
// Give this helper a name for logging
|
74
76
|
virtual void set_log_info(std::string info) = 0;
|
77
|
+
// Get the frame header padding required by this protocol
|
78
|
+
virtual uint8_t frame_header_padding() = 0;
|
79
|
+
// Get the frame footer size required by this protocol
|
80
|
+
virtual uint8_t frame_footer_size() = 0;
|
81
|
+
|
82
|
+
protected:
|
83
|
+
// Common implementation for writing raw data to socket
|
84
|
+
template<typename StateEnum>
|
85
|
+
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
86
|
+
const std::string &info, StateEnum &state, StateEnum failed_state);
|
87
|
+
|
88
|
+
uint8_t frame_header_padding_{0};
|
89
|
+
uint8_t frame_footer_size_{0};
|
75
90
|
};
|
76
91
|
|
77
92
|
#ifdef USE_API_NOISE
|
78
93
|
class APINoiseFrameHelper : public APIFrameHelper {
|
79
94
|
public:
|
80
95
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
81
|
-
: socket_(std::move(socket)), ctx_(std::move(
|
96
|
+
: socket_(std::move(socket)), ctx_(std::move(ctx)) {
|
97
|
+
// Noise header structure:
|
98
|
+
// Pos 0: indicator (0x01)
|
99
|
+
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
100
|
+
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
101
|
+
// Pos 7+: actual payload data
|
102
|
+
frame_header_padding_ = 7;
|
103
|
+
}
|
82
104
|
~APINoiseFrameHelper() override;
|
83
105
|
APIError init() override;
|
84
106
|
APIError loop() override;
|
85
107
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
86
108
|
bool can_write_without_blocking() override;
|
87
|
-
APIError
|
109
|
+
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
88
110
|
std::string getpeername() override { return this->socket_->getpeername(); }
|
89
111
|
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
90
112
|
return this->socket_->getpeername(addr, addrlen);
|
@@ -93,6 +115,10 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|
93
115
|
APIError shutdown(int how) override;
|
94
116
|
// Give this helper a name for logging
|
95
117
|
void set_log_info(std::string info) override { info_ = std::move(info); }
|
118
|
+
// Get the frame header padding required by this protocol
|
119
|
+
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
120
|
+
// Get the frame footer size required by this protocol
|
121
|
+
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
96
122
|
|
97
123
|
protected:
|
98
124
|
struct ParsedFrame {
|
@@ -103,7 +129,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|
103
129
|
APIError try_read_frame_(ParsedFrame *frame);
|
104
130
|
APIError try_send_tx_buf_();
|
105
131
|
APIError write_frame_(const uint8_t *data, size_t len);
|
106
|
-
APIError write_raw_(const struct iovec *iov, int iovcnt)
|
132
|
+
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
|
133
|
+
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
|
134
|
+
}
|
107
135
|
APIError init_handshake_();
|
108
136
|
APIError check_handshake_finished_();
|
109
137
|
void send_explicit_handshake_reject_(const std::string &reason);
|
@@ -111,6 +139,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|
111
139
|
std::unique_ptr<socket::Socket> socket_;
|
112
140
|
|
113
141
|
std::string info_;
|
142
|
+
// Fixed-size header buffer for noise protocol:
|
143
|
+
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
144
|
+
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
|
114
145
|
uint8_t rx_header_buf_[3];
|
115
146
|
size_t rx_header_buf_len_ = 0;
|
116
147
|
std::vector<uint8_t> rx_buf_;
|
@@ -141,13 +172,20 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|
141
172
|
#ifdef USE_API_PLAINTEXT
|
142
173
|
class APIPlaintextFrameHelper : public APIFrameHelper {
|
143
174
|
public:
|
144
|
-
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
|
175
|
+
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {
|
176
|
+
// Plaintext header structure (worst case):
|
177
|
+
// Pos 0: indicator (0x00)
|
178
|
+
// Pos 1-3: payload size varint (up to 3 bytes)
|
179
|
+
// Pos 4-5: message type varint (up to 2 bytes)
|
180
|
+
// Pos 6+: actual payload data
|
181
|
+
frame_header_padding_ = 6;
|
182
|
+
}
|
145
183
|
~APIPlaintextFrameHelper() override = default;
|
146
184
|
APIError init() override;
|
147
185
|
APIError loop() override;
|
148
186
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
149
187
|
bool can_write_without_blocking() override;
|
150
|
-
APIError
|
188
|
+
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
151
189
|
std::string getpeername() override { return this->socket_->getpeername(); }
|
152
190
|
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
153
191
|
return this->socket_->getpeername(addr, addrlen);
|
@@ -156,6 +194,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|
156
194
|
APIError shutdown(int how) override;
|
157
195
|
// Give this helper a name for logging
|
158
196
|
void set_log_info(std::string info) override { info_ = std::move(info); }
|
197
|
+
// Get the frame header padding required by this protocol
|
198
|
+
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
199
|
+
// Get the frame footer size required by this protocol
|
200
|
+
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
159
201
|
|
160
202
|
protected:
|
161
203
|
struct ParsedFrame {
|
@@ -164,12 +206,23 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|
164
206
|
|
165
207
|
APIError try_read_frame_(ParsedFrame *frame);
|
166
208
|
APIError try_send_tx_buf_();
|
167
|
-
APIError write_raw_(const struct iovec *iov, int iovcnt)
|
209
|
+
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
|
210
|
+
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
|
211
|
+
}
|
168
212
|
|
169
213
|
std::unique_ptr<socket::Socket> socket_;
|
170
214
|
|
171
215
|
std::string info_;
|
172
|
-
|
216
|
+
// Fixed-size header buffer for plaintext protocol:
|
217
|
+
// We only need space for the two varints since we validate the indicator byte separately.
|
218
|
+
// To match noise protocol's maximum message size (65535), we need:
|
219
|
+
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
220
|
+
//
|
221
|
+
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
222
|
+
// attempting to process messages with headers that large would likely crash the
|
223
|
+
// ESP32 due to memory constraints.
|
224
|
+
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type)
|
225
|
+
uint8_t rx_header_buf_pos_ = 0;
|
173
226
|
bool rx_header_parsed_ = false;
|
174
227
|
uint32_t rx_header_parsed_type_ = 0;
|
175
228
|
uint32_t rx_header_parsed_len_ = 0;
|