mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 11:26:39 +00:00
Merge remote-tracking branch 'origin/dev' into socket-lwip-raw-udp
This commit is contained in:
@@ -299,7 +299,7 @@ jobs:
|
||||
# Pushes any fixes the hooks made back to the pull request. This step
|
||||
# must keep its default name: the GitHub App that performs the push
|
||||
# locates the workflow run by that name.
|
||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||
- uses: pre-commit-ci/lite-action@062bca0919bc9d6e66755cc05074b70c77e111fc # v1.2.0
|
||||
if: always()
|
||||
with:
|
||||
msg: apply automatic formatting fixes
|
||||
|
||||
@@ -602,6 +602,7 @@ esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/event/* @eoasmxd
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/uart_mux/* @kbx81
|
||||
esphome/components/uart_tcp/* @Bascht74
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
|
||||
@@ -62,16 +62,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await sensor.new_sensor(version_config)
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if resistance_config := config.get(CONF_RESISTANCE):
|
||||
sens = await sensor.new_sensor(resistance_config)
|
||||
cg.add(var.set_resistance(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TVOC, var.set_tvoc)
|
||||
await sensors(CONF_VERSION, var.set_version)
|
||||
await sensors(CONF_RESISTANCE, var.set_resistance)
|
||||
|
||||
|
||||
AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
|
||||
|
||||
@@ -57,10 +57,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_variant(config[CONF_VARIANT]))
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -46,10 +46,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(battery_level_config)
|
||||
cg.add(var.set_battery(sens))
|
||||
|
||||
if illuminance_config := config.get(CONF_ILLUMINANCE):
|
||||
sens = await sensor.new_sensor(illuminance_config)
|
||||
cg.add(var.set_illuminance(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery)
|
||||
await sensors(CONF_ILLUMINANCE, var.set_illuminance)
|
||||
|
||||
@@ -35,10 +35,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_AS3935_ID])
|
||||
|
||||
if distance_config := config.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(hub.set_distance_sensor(sens))
|
||||
|
||||
if lightning_energy_config := config.get(CONF_LIGHTNING_ENERGY):
|
||||
sens = await sensor.new_sensor(lightning_energy_config)
|
||||
cg.add(hub.set_energy_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_DISTANCE, hub.set_distance_sensor)
|
||||
await sensors(CONF_LIGHTNING_ENERGY, hub.set_energy_sensor)
|
||||
|
||||
@@ -241,12 +241,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
if peak_current_config := conf.get(CONF_PEAK_CURRENT):
|
||||
sens = await sensor.new_sensor(peak_current_config)
|
||||
cg.add(var.set_peak_current_sensor(i, sens))
|
||||
if frequency_config := config.get(CONF_FREQUENCY):
|
||||
sens = await sensor.new_sensor(frequency_config)
|
||||
cg.add(var.set_freq_sensor(sens))
|
||||
if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(chip_temperature_config)
|
||||
cg.add(var.set_chip_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FREQUENCY, var.set_freq_sensor)
|
||||
await sensors(CONF_CHIP_TEMPERATURE, var.set_chip_temperature_sensor)
|
||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||
|
||||
@@ -36,14 +36,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
hub = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if phase_cfg := config.get(CONF_PHASE_STATUS):
|
||||
for i, key in enumerate(PHASE_KEYS):
|
||||
if sub_phase_cfg := phase_cfg.get(key):
|
||||
sens = await text_sensor.new_text_sensor(sub_phase_cfg)
|
||||
cg.add(parent.set_phase_status_text_sensor(i, sens))
|
||||
cg.add(hub.set_phase_status_text_sensor(i, sens))
|
||||
|
||||
if freq_status_config := config.get(CONF_FREQUENCY_STATUS):
|
||||
sens = await text_sensor.new_text_sensor(freq_status_config)
|
||||
cg.add(parent.set_freq_status_text_sensor(sens))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_FREQUENCY_STATUS, hub.set_freq_status_text_sensor)
|
||||
|
||||
@@ -44,10 +44,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await register_bedjet_child(var, config)
|
||||
|
||||
if outlet_temperature_sensor := config.get(CONF_OUTLET_TEMPERATURE):
|
||||
sensor_var = await sensor.new_sensor(outlet_temperature_sensor)
|
||||
cg.add(var.set_outlet_temperature_sensor(sensor_var))
|
||||
|
||||
if ambient_temperature_sensor := config.get(CONF_AMBIENT_TEMPERATURE):
|
||||
sensor_var = await sensor.new_sensor(ambient_temperature_sensor)
|
||||
cg.add(var.set_ambient_temperature_sensor(sensor_var))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_OUTLET_TEMPERATURE, var.set_outlet_temperature_sensor)
|
||||
await sensors(CONF_AMBIENT_TEMPERATURE, var.set_ambient_temperature_sensor)
|
||||
|
||||
@@ -88,16 +88,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for d in ["x", "y", "z"]:
|
||||
accel_key = f"acceleration_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_accel_{d}_sensor")(sens))
|
||||
accel_key = f"gyroscope_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_ACCELERATION_X, var.set_accel_x_sensor)
|
||||
await sensors(CONF_GYROSCOPE_X, var.set_gyro_x_sensor)
|
||||
await sensors(CONF_ACCELERATION_Y, var.set_accel_y_sensor)
|
||||
await sensors(CONF_GYROSCOPE_Y, var.set_gyro_y_sensor)
|
||||
await sensors(CONF_ACCELERATION_Z, var.set_accel_z_sensor)
|
||||
await sensors(CONF_GYROSCOPE_Z, var.set_gyro_z_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure)
|
||||
|
||||
@@ -75,14 +75,13 @@ async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
temperature_config = config[CONF_TEMPERATURE]
|
||||
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
pressure_config = config[CONF_PRESSURE]
|
||||
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
|
||||
|
||||
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
||||
|
||||
@@ -80,18 +80,17 @@ async def to_code_base(config: ConfigType) -> MockObj:
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
temperature_config = config[CONF_TEMPERATURE]
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
pressure_config = config[CONF_PRESSURE]
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
|
||||
return var
|
||||
|
||||
@@ -137,9 +137,9 @@ CONFIG_SCHEMA_BASE = cv.Schema(
|
||||
async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
temperature_config = config[CONF_TEMPERATURE]
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
@@ -149,9 +149,8 @@ async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
pressure_config = config[CONF_PRESSURE]
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||
|
||||
|
||||
@@ -54,9 +54,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
if co2_config := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2_config)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
|
||||
@@ -203,6 +203,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
else:
|
||||
cg.add(var.add_source(source))
|
||||
|
||||
if CONF_STD_DEV in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STD_DEV])
|
||||
cg.add(var.set_std_dev_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_STD_DEV, var.set_std_dev_sensor)
|
||||
|
||||
@@ -7,6 +7,7 @@ BYTE_ORDER_BIG = "big_endian"
|
||||
|
||||
CONF_ACCELEROMETER_ODR = "accelerometer_odr"
|
||||
CONF_ACCELEROMETER_RANGE = "accelerometer_range"
|
||||
CONF_ALLOWED_IPS = "allowed_ips"
|
||||
CONF_B_CONSTANT = "b_constant"
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
CONF_BYTE_ORDER = "byte_order"
|
||||
@@ -15,6 +16,7 @@ CONF_CLIMATE_ID = "climate_id"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_COLUMNS = "columns"
|
||||
CONF_CONNECTED = "connected"
|
||||
CONF_CONVERSION_RATE = "conversion_rate"
|
||||
CONF_CRC_ENABLE = "crc_enable"
|
||||
CONF_DATA_BITS = "data_bits"
|
||||
@@ -41,7 +43,9 @@ CONF_ON_SCAN_END = "on_scan_end"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
CONF_PARITY = "parity"
|
||||
CONF_RECEIVER_FREQUENCY = "receiver_frequency"
|
||||
CONF_RECONNECT_INTERVAL = "reconnect_interval"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
CONF_ROLE = "role"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
CONF_SHA256 = "sha256"
|
||||
|
||||
@@ -117,15 +117,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF]))
|
||||
cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY]))
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -61,11 +61,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
pin = await gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
cg.add(var.set_dht_model(config[CONF_MODEL]))
|
||||
|
||||
@@ -46,10 +46,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -54,10 +54,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if pressure := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure_sensor)
|
||||
|
||||
@@ -63,9 +63,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.bool_)
|
||||
cg.add(var.set_lambda(lambda_))
|
||||
if CONF_LAST_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LAST_TIME])
|
||||
cg.add(var.set_last_duty_time_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_LAST_TIME, var.set_last_duty_time_sensor)
|
||||
|
||||
|
||||
# AUTOMATIONS
|
||||
|
||||
@@ -57,14 +57,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
if pressure := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure_sensor)
|
||||
|
||||
@@ -50,10 +50,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -30,19 +30,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_ES8388_ID])
|
||||
if dac_output_config := config.get(CONF_DAC_OUTPUT):
|
||||
s = await select.new_select(
|
||||
dac_output_config,
|
||||
options=["LINE1", "LINE2", "BOTH"],
|
||||
)
|
||||
await cg.register_parented(s, parent)
|
||||
cg.add(parent.set_dac_output_select(s))
|
||||
|
||||
if adc_input_mic_config := config.get(CONF_ADC_INPUT_MIC):
|
||||
s = await select.new_select(
|
||||
adc_input_mic_config,
|
||||
options=["LINE1", "LINE2", "DIFFERENCE"],
|
||||
)
|
||||
await cg.register_parented(s, parent)
|
||||
cg.add(parent.set_adc_input_mic_select(s))
|
||||
hub = await cg.get_variable(config[CONF_ES8388_ID])
|
||||
selects = select.sub_selects(config, parent=hub)
|
||||
await selects(
|
||||
CONF_DAC_OUTPUT, hub.set_dac_output_select, options=["LINE1", "LINE2", "BOTH"]
|
||||
)
|
||||
await selects(
|
||||
CONF_ADC_INPUT_MIC,
|
||||
hub.set_adc_input_mic_select,
|
||||
options=["LINE1", "LINE2", "DIFFERENCE"],
|
||||
)
|
||||
|
||||
@@ -220,6 +220,9 @@ class EthernetComponent final : public Component {
|
||||
protected:
|
||||
void start_connect_();
|
||||
void finish_connect_();
|
||||
#if LWIP_IPV6
|
||||
esp_err_t ensure_ip6_linklocal_();
|
||||
#endif
|
||||
void dump_connect_params_();
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include <lwip/dns.h>
|
||||
#include <cinttypes>
|
||||
#include "esp_event.h"
|
||||
#if USE_NETWORK_IPV6
|
||||
#include <esp_netif_net_stack.h>
|
||||
#include <lwip/netif.h>
|
||||
#endif
|
||||
#ifdef USE_PSRAM
|
||||
#include <esp_psram.h>
|
||||
#endif
|
||||
@@ -509,6 +513,18 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
// Since IDF 5.5 the internal EMAC drops multicast groups that were never added (before,
|
||||
// it passed all multicast), and lwIP never adds all-nodes, so router advertisements
|
||||
// were lost and SLAAC never ran.
|
||||
{
|
||||
uint8_t all_nodes[6] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01};
|
||||
if (esp_err_t filter_err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_ADD_MAC_FILTER, all_nodes);
|
||||
filter_err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 all-nodes multicast filter not added: %s", esp_err_to_name(filter_err));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Register user defined event handers
|
||||
err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
|
||||
@@ -755,6 +771,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
|
||||
global_eth_component->notify_ip_state_listeners_();
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV6
|
||||
// Start SLAAC on link-up, not after the DHCPv4 lease. This also restores the
|
||||
// link-local after a link flap, which clears the IPv6 addresses.
|
||||
if (esp_err_t ll_err = esp_netif_create_ip6_linklocal(global_eth_component->eth_netif_); ll_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed on link-up: %s", esp_err_to_name(ll_err));
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
@@ -791,7 +814,10 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
|
||||
void *event_data) {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
|
||||
global_eth_component->ipv6_count_ += 1;
|
||||
// Count the addresses on the interface, not the events: recreating the link-local
|
||||
// after a link flap fires another event for the same address.
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
global_eth_component->ipv6_count_ = esp_netif_get_all_ip6(global_eth_component->eth_netif_, if_ip6s);
|
||||
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
|
||||
global_eth_component->connected_ =
|
||||
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
|
||||
@@ -806,6 +832,29 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
// Create the link-local address unless the interface already has one, including one still in
|
||||
// duplicate address detection: recreating it would restart DAD. esp_netif_get_ip6_linklocal()
|
||||
// only reports a preferred address, so ask lwIP for the slot state instead.
|
||||
esp_err_t EthernetComponent::ensure_ip6_linklocal_() {
|
||||
if (auto *netif = static_cast<struct netif *>(esp_netif_get_netif_impl(this->eth_netif_)); netif != nullptr) {
|
||||
u8_t state;
|
||||
{
|
||||
LwIPLock lock;
|
||||
state = netif_ip6_addr_state(netif, 0);
|
||||
}
|
||||
if (ip6_addr_istentative(state) || ip6_addr_isvalid(state)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
#if USE_NETWORK_IPV6
|
||||
// Retry IPv6 link-local setup if it failed during initial connect
|
||||
@@ -816,10 +865,7 @@ void EthernetComponent::finish_connect_() {
|
||||
// - Cable unplugged/network interruption (#10705)
|
||||
// We can now retry since we're in CONNECTED state and the interface is definitely up.
|
||||
if (!this->ipv6_setup_done_) {
|
||||
esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
|
||||
}
|
||||
this->ensure_ip6_linklocal_();
|
||||
// Always set the flag to prevent continuous retries
|
||||
// If IPv6 setup fails here with the interface up and stable, it's
|
||||
// likely a persistent issue (IPv6 disabled at router, hardware
|
||||
@@ -833,7 +879,9 @@ void EthernetComponent::finish_connect_() {
|
||||
void EthernetComponent::start_connect_() {
|
||||
global_eth_component->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
// Recount rather than zero: addresses that survive a reconnect are not announced again.
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
global_eth_component->ipv6_count_ = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
this->ipv6_setup_done_ = false;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
@@ -912,7 +960,7 @@ void EthernetComponent::start_connect_() {
|
||||
// - At bootup when link isn't ready (#10281)
|
||||
// - After disconnection/cable unplugged (#10705)
|
||||
// We'll retry in finish_connect_() if it fails here.
|
||||
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
err = this->ensure_ip6_linklocal_();
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
|
||||
// This is a programming error, not a transient failure
|
||||
|
||||
@@ -55,9 +55,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
|
||||
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
|
||||
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -49,10 +49,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
|
||||
@@ -66,13 +66,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temp_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temp_config)
|
||||
cg.add(var.set_temp_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temp_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
cg.add(var.set_power_mode(config[CONF_POWER_MODE]))
|
||||
|
||||
|
||||
@@ -35,16 +35,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
|
||||
hub = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
|
||||
if (conf := config.get(CONF_DOOR_STATE)) is not None:
|
||||
var = await text_sensor.new_text_sensor(conf, parent)
|
||||
var = await text_sensor.new_text_sensor(conf, hub)
|
||||
await cg.register_component(var, conf)
|
||||
# Only the identity sensors need the exchange with the motor compiled in.
|
||||
if CONF_SERIAL_NUMBER in config or CONF_VERSION in config:
|
||||
cg.add_define("USE_HOERMANN_HCP_IDENTITY")
|
||||
if (conf := config.get(CONF_SERIAL_NUMBER)) is not None:
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(parent.set_serial_number_text_sensor(sens))
|
||||
if (conf := config.get(CONF_VERSION)) is not None:
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(parent.set_version_text_sensor(sens))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_SERIAL_NUMBER, hub.set_serial_number_text_sensor)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_text_sensor)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -56,14 +56,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_honeywellabp_min_pressure(conf[CONF_MIN_PRESSURE]))
|
||||
cg.add(var.set_honeywellabp_max_pressure(conf[CONF_MAX_PRESSURE]))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
@@ -63,13 +63,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
pressure_config = config[CONF_PRESSURE]
|
||||
cg.add(var.set_min_pressure(pressure_config[CONF_MIN_PRESSURE]))
|
||||
cg.add(var.set_max_pressure(pressure_config[CONF_MAX_PRESSURE]))
|
||||
cg.add(var.set_transfer_function(pressure_config[TRANSFER_FUNCTION]))
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
@@ -50,10 +50,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity)
|
||||
cg.add(var.set_humidity(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity)
|
||||
|
||||
@@ -47,12 +47,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if co2_config := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2_config)
|
||||
cg.add(var.set_co2(sens))
|
||||
|
||||
if tvoc_config := config.get(CONF_TVOC):
|
||||
sens = await sensor.new_sensor(tvoc_config)
|
||||
cg.add(var.set_tvoc(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2)
|
||||
await sensors(CONF_TVOC, var.set_tvoc)
|
||||
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
@@ -48,9 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(internal_temperature_config)
|
||||
cg.add(var.set_internal_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_INTERNAL_TEMPERATURE, var.set_internal_temperature_sensor)
|
||||
|
||||
@@ -79,15 +79,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_pack_size(config.get(CONF_SIZE)))
|
||||
cg.add(var.set_pack_voltage(BATTERY_VOLTAGE_OPTIONS[config[CONF_VOLTAGE]]))
|
||||
|
||||
if voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery_remaining_sensor)
|
||||
|
||||
if level_config := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(level_config)
|
||||
cg.add(var.set_battery_remaining_sensor(sens))
|
||||
|
||||
if temp_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temp_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_thermistor_b_constant(temp_config[CONF_B_CONSTANT]))
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
b_constant = config[CONF_TEMPERATURE][CONF_B_CONSTANT]
|
||||
cg.add(var.set_thermistor_b_constant(b_constant))
|
||||
|
||||
@@ -25,8 +25,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
lt_component = await cg.get_variable(config[CONF_LIBRETINY])
|
||||
hub = await cg.get_variable(config[CONF_LIBRETINY])
|
||||
|
||||
if CONF_VERSION in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
|
||||
cg.add(lt_component.set_version_sensor(sens))
|
||||
text_sensors = text_sensor.sub_text_sensors(config)
|
||||
await text_sensors(CONF_VERSION, hub.set_version_sensor)
|
||||
|
||||
@@ -50,10 +50,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure_sensor)
|
||||
|
||||
@@ -53,13 +53,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_remaining_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery_remaining_sensor)
|
||||
|
||||
|
||||
MAX17043_ACTION_SCHEMA = maybe_simple_id(
|
||||
|
||||
@@ -41,6 +41,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
if CONF_REFERENCE_TEMPERATURE in config:
|
||||
tc_ref = await sensor.new_sensor(config[CONF_REFERENCE_TEMPERATURE])
|
||||
cg.add(var.set_reference_sensor(tc_ref))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_REFERENCE_TEMPERATURE, var.set_reference_sensor)
|
||||
|
||||
@@ -71,12 +71,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE]))
|
||||
|
||||
if CONF_HOT_JUNCTION in config:
|
||||
conf = config[CONF_HOT_JUNCTION]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_hot_junction(sens))
|
||||
|
||||
if CONF_COLD_JUNCTION in config:
|
||||
conf = config[CONF_COLD_JUNCTION]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_cold_junction(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_HOT_JUNCTION, var.set_hot_junction)
|
||||
await sensors(CONF_COLD_JUNCTION, var.set_cold_junction)
|
||||
|
||||
@@ -77,13 +77,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
if (
|
||||
automatic_baseline_calibration := config.get(
|
||||
|
||||
@@ -139,21 +139,14 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION]))
|
||||
cg.add(var.set_hallconf(config[CONF_HALLCONF]))
|
||||
|
||||
if CONF_X_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_X_AXIS])
|
||||
cg.add(var.set_x_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_X_AXIS, var.set_x_sensor):
|
||||
cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_Y_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_Y_AXIS])
|
||||
cg.add(var.set_y_sensor(sens))
|
||||
if await sensors(CONF_Y_AXIS, var.set_y_sensor):
|
||||
cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_Z_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_Z_AXIS])
|
||||
cg.add(var.set_z_sensor(sens))
|
||||
if await sensors(CONF_Z_AXIS, var.set_z_sensor):
|
||||
cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_t_sensor(sens))
|
||||
if await sensors(CONF_TEMPERATURE, var.set_t_sensor):
|
||||
cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING]))
|
||||
if CONF_DRDY_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
|
||||
|
||||
@@ -53,12 +53,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_AMBIENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_AMBIENT])
|
||||
cg.add(var.set_ambient_sensor(sens))
|
||||
|
||||
if CONF_OBJECT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OBJECT])
|
||||
cg.add(var.set_object_sensor(sens))
|
||||
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_AMBIENT, var.set_ambient_sensor)
|
||||
if await sensors(CONF_OBJECT, var.set_object_sensor):
|
||||
cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY]))
|
||||
|
||||
@@ -45,12 +45,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if x_config := config.get(CONF_FIELD_STRENGTH_X):
|
||||
sens = await sensor.new_sensor(x_config)
|
||||
cg.add(var.set_x_sensor(sens))
|
||||
if y_config := config.get(CONF_FIELD_STRENGTH_Y):
|
||||
sens = await sensor.new_sensor(y_config)
|
||||
cg.add(var.set_y_sensor(sens))
|
||||
if z_config := config.get(CONF_FIELD_STRENGTH_Z):
|
||||
sens = await sensor.new_sensor(z_config)
|
||||
cg.add(var.set_z_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FIELD_STRENGTH_X, var.set_x_sensor)
|
||||
await sensors(CONF_FIELD_STRENGTH_Y, var.set_y_sensor)
|
||||
await sensors(CONF_FIELD_STRENGTH_Z, var.set_z_sensor)
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Literal, NamedTuple
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
from esphome.components.const import CONF_ROLE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_CONTINUOUS, CONF_FLOW_CONTROL_PIN, CONF_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
@@ -45,7 +46,6 @@ MULTI_CONF = True
|
||||
|
||||
CONF_ALLOW_BROADCAST_READ = "allow_broadcast_read"
|
||||
CONF_EXPECT_BROADCAST_WRITE_RESPONSE = "expect_broadcast_write_response"
|
||||
CONF_ROLE = "role"
|
||||
CONF_MODBUS_ID = "modbus_id"
|
||||
CONF_SEND_WAIT_TIME = "send_wait_time"
|
||||
CONF_TURNAROUND_TIME = "turnaround_time"
|
||||
|
||||
@@ -64,13 +64,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure(sens))
|
||||
elif CONF_ALTITUDE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ALTITUDE])
|
||||
cg.add(var.set_altitude(sens))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure)
|
||||
await sensors(CONF_ALTITUDE, var.set_altitude)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature)
|
||||
|
||||
@@ -70,16 +70,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for d in ["x", "y", "z"]:
|
||||
accel_key = f"accel_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_accel_{d}_sensor")(sens))
|
||||
accel_key = f"gyro_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_ACCEL_X, var.set_accel_x_sensor)
|
||||
await sensors(CONF_GYRO_X, var.set_gyro_x_sensor)
|
||||
await sensors(CONF_ACCEL_Y, var.set_accel_y_sensor)
|
||||
await sensors(CONF_GYRO_Y, var.set_gyro_y_sensor)
|
||||
await sensors(CONF_ACCEL_Z, var.set_accel_z_sensor)
|
||||
await sensors(CONF_GYRO_Z, var.set_gyro_z_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
@@ -71,16 +71,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for d in ["x", "y", "z"]:
|
||||
accel_key = f"accel_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_accel_{d}_sensor")(sens))
|
||||
accel_key = f"gyro_{d}"
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_ACCEL_X, var.set_accel_x_sensor)
|
||||
await sensors(CONF_GYRO_X, var.set_gyro_x_sensor)
|
||||
await sensors(CONF_ACCEL_Y, var.set_accel_y_sensor)
|
||||
await sensors(CONF_GYRO_Y, var.set_gyro_y_sensor)
|
||||
await sensors(CONF_ACCEL_Z, var.set_accel_z_sensor)
|
||||
await sensors(CONF_GYRO_Z, var.set_gyro_z_sensor)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
|
||||
@@ -50,10 +50,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if pressure := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure_sensor)
|
||||
|
||||
@@ -68,17 +68,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_PRESSURE, var.set_pressure_sensor)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
if await sensors(CONF_HUMIDITY, var.set_humidity_sensor):
|
||||
humidity_config = config[CONF_HUMIDITY]
|
||||
humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID])
|
||||
await i2c.register_i2c_device(humidity_device, humidity_config)
|
||||
cg.add(var.set_humidity_device(humidity_device))
|
||||
|
||||
@@ -44,10 +44,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if raw_pressure_config := config.get(CONF_RAW_PRESSURE):
|
||||
sens = await sensor.new_sensor(raw_pressure_config)
|
||||
cg.add(var.set_raw_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_RAW_PRESSURE, var.set_raw_pressure_sensor)
|
||||
|
||||
@@ -9,6 +9,7 @@ import subprocess
|
||||
|
||||
from esphome import pins
|
||||
from esphome.build_helpers import pch
|
||||
from esphome.build_helpers.ccache import resolve_ccache_path
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import (
|
||||
add_extra_script,
|
||||
@@ -584,7 +585,7 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
|
||||
raise EsphomeError("Not implemented yet")
|
||||
check_and_install()
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
env = get_build_env(None) # no compile, just nrfutil
|
||||
build_dir = CORE.relative_pioenvs_path(CORE.name)
|
||||
dfu_package = build_dir / "firmware.zip"
|
||||
if not dfu_package.is_file():
|
||||
@@ -666,7 +667,7 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
|
||||
if not CORE.using_toolchain_platformio:
|
||||
check_and_install()
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
env = get_build_env(resolve_ccache_path()) # west flash may rebuild
|
||||
build_dir = CORE.relative_pioenvs_path(CORE.name)
|
||||
west_cmd = [
|
||||
str(paths["python_executable"]),
|
||||
@@ -949,7 +950,10 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
check_and_install()
|
||||
|
||||
paths = get_build_paths()
|
||||
env = get_build_env()
|
||||
# Depend mode in the shared ccache settings keeps the .gch sound
|
||||
# across Kconfig flips.
|
||||
ccache = resolve_ccache_path()
|
||||
env = get_build_env(ccache)
|
||||
|
||||
pch_on = _pch_usable()
|
||||
cmake_lists_changed = _generate_cmake_lists(pch_on)
|
||||
@@ -976,8 +980,6 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
|
||||
if pch_on:
|
||||
pch.log_pch_in_use()
|
||||
# Zephyr turns ccache on by itself when it is installed
|
||||
env.update(pch.ccache_pch_env())
|
||||
_write_pch_checksum(build_dir, source_dir)
|
||||
|
||||
west_cmd = _west_build_command(
|
||||
|
||||
@@ -277,7 +277,7 @@ def generate_compile_commands(work_dir: Path, platformio_ini: Path) -> Path:
|
||||
]
|
||||
if not run_command_ok(
|
||||
west_cmd,
|
||||
env=get_build_env(),
|
||||
env=get_build_env(None), # configure only, nothing compiles
|
||||
stream_output=True,
|
||||
cwd=str(paths["framework_path"]),
|
||||
):
|
||||
|
||||
@@ -10,6 +10,7 @@ import platform
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from esphome.build_helpers.ccache import ccache_env
|
||||
from esphome.build_helpers.tools_cache import SDK_NRF_TOOLS_CACHE, tools_cache_path
|
||||
from esphome.components.zephyr.const import KEY_SYSBUILD, KEY_ZEPHYR
|
||||
import esphome.config_validation as cv
|
||||
@@ -194,7 +195,14 @@ def get_build_paths() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def get_build_env() -> dict:
|
||||
def get_build_env(ccache: str | None) -> dict:
|
||||
"""Build the west/sdk-nrf process environment.
|
||||
|
||||
``ccache`` is the resolved binary (resolve_ccache_path), or None when
|
||||
ccache is disabled or the caller never compiles; it brings the shared
|
||||
managed-ccache settings and the pch sloppiness, so every caller that
|
||||
may compile gets the same cache.
|
||||
"""
|
||||
version = _get_version_str()
|
||||
venv_bin_dir = get_python_env_executable_path(
|
||||
_get_python_env_path(version), "python"
|
||||
@@ -211,6 +219,12 @@ def get_build_env() -> dict:
|
||||
# non-root builds failed to locate the SDK with it, while
|
||||
# ZEPHYR_SDK_INSTALL_DIR fixed the same invocation.
|
||||
env["ZEPHYR_SDK_INSTALL_DIR"] = str(_get_toolchain_path(_get_toolchain_version()))
|
||||
if ccache is None:
|
||||
# Zephyr wraps compiles with any ccache it finds; unmanaged it
|
||||
# must not cache (a sysbuild image never sees USE_CCACHE=0).
|
||||
env.setdefault("CCACHE_DISABLE", "1")
|
||||
else:
|
||||
env.update(ccache_env(ccache, SDK_NRF_TOOLS_CACHE))
|
||||
return env
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
from esphome.components.const import CONF_ROLE
|
||||
from esphome.components.openthread.const import (
|
||||
CONF_EXT_PAN_ID,
|
||||
CONF_NETWORK_KEY,
|
||||
@@ -10,7 +11,6 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_IP_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_ROLE = "role"
|
||||
CONF_RLOC16 = "rloc16"
|
||||
CONF_EUI64 = "eui64"
|
||||
CONF_EXT_ADDR = "ext_addr"
|
||||
|
||||
@@ -62,6 +62,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_PM_2_5 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_2_5])
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PM_2_5, var.set_pm_2_5_sensor)
|
||||
|
||||
@@ -74,14 +74,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_sensor_type(config[CONF_TYPE]))
|
||||
|
||||
if pm_1_0_config := config.get(CONF_PM_1_0):
|
||||
sens = await sensor.new_sensor(pm_1_0_config)
|
||||
cg.add(var.set_pm_1_0_sensor(sens))
|
||||
|
||||
if pm_2_5_config := config.get(CONF_PM_2_5):
|
||||
sens = await sensor.new_sensor(pm_2_5_config)
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
||||
|
||||
if pm_10_0_config := config.get(CONF_PM_10_0):
|
||||
sens = await sensor.new_sensor(pm_10_0_config)
|
||||
cg.add(var.set_pm_10_0_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PM_1_0, var.set_pm_1_0_sensor)
|
||||
await sensors(CONF_PM_2_5, var.set_pm_2_5_sensor)
|
||||
await sensors(CONF_PM_10_0, var.set_pm_10_0_sensor)
|
||||
|
||||
@@ -140,9 +140,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_falling_edge_mode(count[CONF_FALLING_EDGE]))
|
||||
cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER]))
|
||||
|
||||
if CONF_TOTAL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL])
|
||||
cg.add(var.set_total_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TOTAL, var.set_total_sensor)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -92,9 +92,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_timeout_us(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_filter_mode(config[CONF_INTERNAL_FILTER_MODE]))
|
||||
|
||||
if CONF_TOTAL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL])
|
||||
cg.add(var.set_total_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TOTAL, var.set_total_sensor)
|
||||
|
||||
|
||||
automation.register_apply_action(
|
||||
|
||||
@@ -47,9 +47,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_RADON in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON])
|
||||
cg.add(var.set_radon(sens))
|
||||
if CONF_RADON_LONG_TERM in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
|
||||
cg.add(var.set_radon_long_term(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_RADON, var.set_radon)
|
||||
await sensors(CONF_RADON_LONG_TERM, var.set_radon_long_term)
|
||||
|
||||
@@ -30,9 +30,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_RD03D_ID])
|
||||
|
||||
if target_config := config.get(CONF_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(target_config)
|
||||
cg.add(hub.set_target_binary_sensor(sens))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_TARGET, hub.set_target_binary_sensor)
|
||||
|
||||
for i in range(MAX_TARGETS):
|
||||
if target_config := config.get(f"target_{i + 1}"):
|
||||
|
||||
@@ -79,9 +79,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_RD03D_ID])
|
||||
|
||||
if target_count_config := config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(hub.set_target_count_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TARGET_COUNT, hub.set_target_count_sensor)
|
||||
|
||||
for i in range(MAX_TARGETS):
|
||||
if target_config := config.get(f"target_{i + 1}"):
|
||||
|
||||
@@ -93,10 +93,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_update_interval_min(update_interval))
|
||||
cg.add(var.set_rx_mode_only(config[CONF_RX_ONLY]))
|
||||
|
||||
if CONF_PM_2_5 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_2_5])
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
||||
|
||||
if CONF_PM_10_0 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_10_0])
|
||||
cg.add(var.set_pm_10_0_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PM_2_5, var.set_pm_2_5_sensor)
|
||||
await sensors(CONF_PM_10_0, var.set_pm_10_0_sensor)
|
||||
|
||||
@@ -53,9 +53,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
|
||||
@@ -56,10 +56,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_heater_enabled(config[CONF_HEATER_ENABLED]))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -51,10 +51,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -19,8 +19,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID])
|
||||
hub = await cg.get_variable(config[CONF_SIM800L_ID])
|
||||
|
||||
if CONF_REGISTERED in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_REGISTERED])
|
||||
cg.add(sim800l_component.set_registered_binary_sensor(sens))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_REGISTERED, hub.set_registered_binary_sensor)
|
||||
|
||||
@@ -27,8 +27,7 @@ CONFIG_SCHEMA = {
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID])
|
||||
hub = await cg.get_variable(config[CONF_SIM800L_ID])
|
||||
|
||||
if CONF_RSSI in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RSSI])
|
||||
cg.add(sim800l_component.set_rssi_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_RSSI, hub.set_rssi_sensor)
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from ipaddress import IPv4Address, IPv4Network
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_ROLE
|
||||
from esphome.config_helpers import filter_source_files_from_defines
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
socket_ns = cg.esphome_ns.namespace("socket")
|
||||
Ipv4AllowEntry = socket_ns.struct("Ipv4AllowEntry")
|
||||
|
||||
CONF_IMPLEMENTATION = "implementation"
|
||||
IMPLEMENTATION_LWIP_TCP = "lwip_tcp"
|
||||
IMPLEMENTATION_LWIP_SOCKETS = "lwip_sockets"
|
||||
@@ -140,6 +145,38 @@ def require_wake_loop_threadsafe() -> None:
|
||||
cg.add_define("USE_SOCKET_SELECT_SUPPORT")
|
||||
|
||||
|
||||
# For an Ipv4Allow config option; a sanity cap on the list length.
|
||||
IPV4_ALLOW_SCHEMA = cv.All(cv.ensure_list(cv.ipv4network), cv.Length(max=255))
|
||||
|
||||
|
||||
def _network_order(addr: IPv4Address) -> int:
|
||||
"""The s_addr value for addr on the little endian targets."""
|
||||
return int.from_bytes(addr.packed, "little")
|
||||
|
||||
|
||||
def add_ipv4_allow(
|
||||
setter: cg.MockObj, networks: list[IPv4Network], owner_id: ID | str
|
||||
) -> None:
|
||||
"""Emit a flash array for validated IPV4_ALLOW_SCHEMA entries and wire it to setter.
|
||||
|
||||
PROGMEM on esp8266. Emits nothing for an empty list.
|
||||
"""
|
||||
if not networks:
|
||||
return
|
||||
cg.add_define("USE_SOCKET_IPV4_ALLOW")
|
||||
entries = [
|
||||
cg.StructInitializer(
|
||||
Ipv4AllowEntry,
|
||||
("addr", _network_order(net.network_address)),
|
||||
("mask", _network_order(net.netmask)),
|
||||
)
|
||||
for net in networks
|
||||
]
|
||||
arr_id = ID(f"{owner_id}_ipv4_allow", is_declaration=True, type=Ipv4AllowEntry)
|
||||
arr = cg.progmem_array(arr_id, cg.ArrayInitializer(*entries))
|
||||
cg.add(setter(arr, len(entries)))
|
||||
|
||||
|
||||
def require_ipv4_resolve() -> None:
|
||||
"""Compile the shared IPv4 lookup; call from a consumer's to_code."""
|
||||
cg.add_define("USE_SOCKET_IPV4_RESOLVE")
|
||||
@@ -151,6 +188,23 @@ def require_tcp_client_link() -> None:
|
||||
cg.add_define("USE_SOCKET_TCP_CLIENT_LINK")
|
||||
|
||||
|
||||
def require_tcp_listener() -> None:
|
||||
"""Compile the TCP listener; call from a server role's to_code."""
|
||||
require_tcp_client_link()
|
||||
cg.add_define("USE_SOCKET_TCP_LISTENER")
|
||||
|
||||
|
||||
def consume_role_sockets(component: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Socket accounting for a role keyed client or server schema."""
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
if config[CONF_ROLE] == "server":
|
||||
consume_sockets(1, component, SocketType.TCP_LISTEN)(config)
|
||||
return consume_sockets(1, component)(config)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
@@ -206,5 +260,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
"lwip_sockets_impl.cpp": "USE_SOCKET_IMPL_LWIP_SOCKETS",
|
||||
"ipv4_resolve.cpp": "USE_SOCKET_IPV4_RESOLVE",
|
||||
"tcp_client_link.cpp": "USE_SOCKET_TCP_CLIENT_LINK",
|
||||
"tcp_listener.cpp": "USE_SOCKET_TCP_LISTENER",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "headers.h"
|
||||
#include "socket.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
/// One allowed IPv4 network, network byte order, host bits cleared.
|
||||
/// Lives in flash; read via progmem_memcpy.
|
||||
struct Ipv4AllowEntry {
|
||||
uint32_t addr;
|
||||
uint32_t mask;
|
||||
};
|
||||
|
||||
/// IPv4 peers that may connect. An empty list allows every peer.
|
||||
class Ipv4Allow {
|
||||
public:
|
||||
void set(const Ipv4AllowEntry *entries, size_t count) {
|
||||
this->entries_ = entries;
|
||||
this->count_ = count;
|
||||
}
|
||||
|
||||
/// A v4 mapped IPv6 peer is unwrapped; any other family fails a non empty list.
|
||||
bool allows(const struct sockaddr *peer) const {
|
||||
if (this->count_ == 0) {
|
||||
return true;
|
||||
}
|
||||
uint32_t addr;
|
||||
return sockaddr_to_ipv4(peer, &addr) && this->allows(addr);
|
||||
}
|
||||
|
||||
/// addr is network byte order, as it sits in a sockaddr_in.
|
||||
bool allows(uint32_t addr) const {
|
||||
if (this->count_ == 0) {
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i != this->count_; i++) {
|
||||
Ipv4AllowEntry e = this->entry(i);
|
||||
if ((addr & e.mask) == e.addr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size() const { return this->count_; }
|
||||
/// A copy of entry i, read from flash.
|
||||
Ipv4AllowEntry entry(size_t i) const {
|
||||
Ipv4AllowEntry e;
|
||||
progmem_memcpy(&e, &this->entries_[i], sizeof(e));
|
||||
return e;
|
||||
}
|
||||
|
||||
private:
|
||||
const Ipv4AllowEntry *entries_{nullptr};
|
||||
size_t count_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::socket
|
||||
@@ -78,6 +78,25 @@ static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out) {
|
||||
if (addr->sa_family == AF_INET) {
|
||||
*out = reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr;
|
||||
return true;
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
if (addr->sa_family == AF_INET6) {
|
||||
// ::ffff:a.b.c.d; s6_addr is the portable byte view on every stack.
|
||||
static constexpr uint8_t V4_MAPPED_PREFIX[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF};
|
||||
const uint8_t *bytes = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr.s6_addr;
|
||||
if (memcmp(bytes, V4_MAPPED_PREFIX, sizeof(V4_MAPPED_PREFIX)) == 0) {
|
||||
memcpy(out, bytes + sizeof(V4_MAPPED_PREFIX), sizeof(*out));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format sockaddr into caller-provided buffer, returns length written (excluding null)
|
||||
size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
if (addr_ptr->sa_family == AF_INET && len >= sizeof(const struct sockaddr_in)) {
|
||||
@@ -88,29 +107,10 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s
|
||||
#if USE_NETWORK_IPV6
|
||||
else if (addr_ptr->sa_family == AF_INET6 && len >= sizeof(sockaddr_in6)) {
|
||||
const auto *addr = reinterpret_cast<const struct sockaddr_in6 *>(addr_ptr);
|
||||
#ifdef USE_HOST
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4 (POSIX layout, no LWIP union)
|
||||
if (IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr) &&
|
||||
esphome_inet_ntop4(&addr->sin6_addr.s6_addr[12], buf.data(), buf.size()) != nullptr) {
|
||||
uint32_t v4;
|
||||
// Format a v4 mapped peer as plain IPv4.
|
||||
if (sockaddr_to_ipv4(addr_ptr, &v4) && esphome_inet_ntop4(&v4, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4. Zephyr uses the standard POSIX
|
||||
// s6_addr layout (not the LWIP union) but provides no IN6_IS_ADDR_V4MAPPED macro, so
|
||||
// detect the ::ffff:0:0/96 prefix directly on the address words.
|
||||
if (addr->sin6_addr.s6_addr32[0] == 0 && addr->sin6_addr.s6_addr32[1] == 0 &&
|
||||
addr->sin6_addr.s6_addr32[2] == htonl(0xFFFF) &&
|
||||
esphome_inet_ntop4(&addr->sin6_addr.s6_addr32[3], buf.data(), buf.size()) != nullptr) {
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#elif !defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4 (LWIP layout)
|
||||
if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 &&
|
||||
addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) &&
|
||||
esphome_inet_ntop4(&addr->sin6_addr.un.u32_addr[3], buf.data(), buf.size()) != nullptr) {
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#endif
|
||||
if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
|
||||
@@ -178,6 +178,10 @@ inline std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int prot
|
||||
/// @return Size of the sockaddr structure used, or 0 on error
|
||||
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port);
|
||||
|
||||
/// Network order IPv4 of an AF_INET or v4 mapped AF_INET6 peer; false for anything else.
|
||||
/// addr must hold the family's full struct.
|
||||
bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out);
|
||||
|
||||
/// Convenience overload for std::string (backward compatible).
|
||||
inline socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
|
||||
return set_sockaddr(addr, addrlen, ip_address.c_str(), port);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
@@ -114,7 +115,7 @@ ssize_t TcpClientLink::read(uint8_t *buf, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t TcpClientLink::write(const uint8_t *buf, size_t len) {
|
||||
ssize_t TcpClientLink::write_(const uint8_t *buf, size_t len) {
|
||||
if (!this->connected_ || len == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -129,6 +130,26 @@ ssize_t TcpClientLink::write(const uint8_t *buf, size_t len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t TcpClientLink::queue(const uint8_t *data, size_t len) {
|
||||
size_t room = this->tx_free();
|
||||
if (len > room) {
|
||||
len = room;
|
||||
}
|
||||
std::memcpy(this->tx_ + this->tx_len_, data, len);
|
||||
this->tx_len_ += static_cast<uint16_t>(len);
|
||||
return len;
|
||||
}
|
||||
|
||||
void TcpClientLink::flush_tx_slow_() {
|
||||
ssize_t sent = this->write_(this->tx_, this->tx_len_);
|
||||
if (sent > 0) {
|
||||
this->tx_len_ -= static_cast<uint16_t>(sent);
|
||||
if (this->tx_len_ != 0) {
|
||||
std::memmove(this->tx_, this->tx_ + sent, this->tx_len_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClientLink::close() {
|
||||
if (this->sock_ != nullptr) {
|
||||
this->sock_->shutdown(SHUT_RDWR);
|
||||
@@ -136,6 +157,7 @@ void TcpClientLink::close() {
|
||||
this->sock_.reset();
|
||||
}
|
||||
this->connected_ = false;
|
||||
this->tx_len_ = 0;
|
||||
this->resolved_.forget();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
namespace esphome::socket {
|
||||
|
||||
/// A reconnecting TCP stream driven from loop(). Owns the socket, the DNS
|
||||
/// lookup and the retry backoff. A fatal read/write error closes the link
|
||||
/// and schedules the next attempt; the caller sees the edge via connected().
|
||||
/// lookup, the retry backoff and the outgoing buffer. A fatal read/write
|
||||
/// error closes the link and schedules the next attempt; the caller sees
|
||||
/// the edge via connected().
|
||||
class TcpClientLink {
|
||||
public:
|
||||
void set_host(const char *host) { this->host_ = StringRef(host); }
|
||||
@@ -41,7 +42,21 @@ class TcpClientLink {
|
||||
void adopt(std::unique_ptr<Socket> sock);
|
||||
/// Returns bytes moved, 0 when nothing can move now, -1 when the link dropped.
|
||||
ssize_t read(uint8_t *buf, size_t len);
|
||||
ssize_t write(const uint8_t *buf, size_t len);
|
||||
/// Copy into the outgoing buffer; returns how many bytes fit.
|
||||
size_t queue(const uint8_t *data, size_t len);
|
||||
/// Direct access to the buffer's free tail. Fill at most tx_free() bytes,
|
||||
/// then tx_commit() the count; neither is bounds checked.
|
||||
uint8_t *tx_tail() { return this->tx_ + this->tx_len_; }
|
||||
void tx_commit(size_t len) { this->tx_len_ += static_cast<uint16_t>(len); }
|
||||
size_t tx_free() const { return this->connected_ ? TX_BUFFER_SIZE - this->tx_len_ : 0; }
|
||||
/// Send the front of the buffer; true once it is empty.
|
||||
/// A partial write keeps the rest; inline no-op while nothing is queued.
|
||||
bool flush_tx() {
|
||||
if (this->tx_len_ != 0) {
|
||||
this->flush_tx_slow_();
|
||||
}
|
||||
return this->tx_len_ == 0;
|
||||
}
|
||||
/// Close without scheduling a reconnect (shutdown).
|
||||
void close();
|
||||
|
||||
@@ -54,6 +69,11 @@ class TcpClientLink {
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr size_t TX_BUFFER_SIZE = 1024;
|
||||
|
||||
/// The raw stream write behind flush_tx(); drops the link on a fatal error.
|
||||
ssize_t write_(const uint8_t *buf, size_t len);
|
||||
void flush_tx_slow_();
|
||||
void poll_slow_();
|
||||
void try_connect_();
|
||||
/// Close after a failure, log what and errno, schedule the next attempt.
|
||||
@@ -66,7 +86,9 @@ class TcpClientLink {
|
||||
uint32_t reconnect_interval_ms_{5000};
|
||||
Ipv4Resolve resolved_;
|
||||
uint16_t port_{0};
|
||||
uint16_t tx_len_{0};
|
||||
bool connected_{false};
|
||||
uint8_t tx_[TX_BUFFER_SIZE]{};
|
||||
};
|
||||
|
||||
} // namespace esphome::socket
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "tcp_listener.h"
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <span>
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
// One client at a time; a second connection waits in the stack until the first drops.
|
||||
static constexpr int LISTEN_BACKLOG = 1;
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
static constexpr uint32_t REJECT_LOG_INTERVAL_MS = 5000;
|
||||
#endif
|
||||
|
||||
void TcpListener::try_listen_(TcpClientLink &link) {
|
||||
this->listen_ = socket_ip_loop_monitored(SOCK_STREAM, IPPROTO_TCP);
|
||||
int err = errno;
|
||||
if (this->listen_ != nullptr) {
|
||||
int yes = 1;
|
||||
this->listen_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
struct sockaddr_storage local;
|
||||
socklen_t local_len = set_sockaddr_any(reinterpret_cast<struct sockaddr *>(&local), sizeof(local), link.port());
|
||||
// A blocking listener would stall loop() inside accept(), so its
|
||||
// setblocking result is part of the success condition.
|
||||
if (this->listen_->setblocking(false) == 0 && local_len != 0 &&
|
||||
this->listen_->bind(reinterpret_cast<struct sockaddr *>(&local), local_len) == 0 &&
|
||||
this->listen_->listen(LISTEN_BACKLOG) == 0) {
|
||||
ESP_LOGI(this->tag_, "Listening on %u", link.port());
|
||||
return;
|
||||
}
|
||||
// Captured before reset(); the close inside can overwrite errno.
|
||||
err = errno;
|
||||
this->listen_.reset();
|
||||
}
|
||||
ESP_LOGW(this->tag_, "Listen on %u failed: %d", link.port(), err);
|
||||
link.note_attempt();
|
||||
}
|
||||
|
||||
void TcpListener::accept_(TcpClientLink &link) {
|
||||
struct sockaddr_storage peer {};
|
||||
socklen_t peer_len = sizeof(peer);
|
||||
auto client = this->listen_->accept_loop_monitored(reinterpret_cast<struct sockaddr *>(&peer), &peer_len);
|
||||
if (client == nullptr) {
|
||||
// A reset during the handshake or a signal only affects that connection.
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR) {
|
||||
return;
|
||||
}
|
||||
// Rebuild the listener after the backoff instead of spinning on it.
|
||||
int err = errno;
|
||||
this->listen_.reset();
|
||||
ESP_LOGW(this->tag_, "Accept failed: %d", err);
|
||||
link.note_attempt();
|
||||
return;
|
||||
}
|
||||
const auto *sa = reinterpret_cast<const struct sockaddr *>(&peer);
|
||||
char text[SOCKADDR_STR_LEN];
|
||||
format_sockaddr_to(sa, peer_len, std::span<char, SOCKADDR_STR_LEN>(text));
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
if (!this->allow_.allows(sa)) {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if (this->last_reject_log_ms_ == 0 || now - this->last_reject_log_ms_ >= REJECT_LOG_INTERVAL_MS) {
|
||||
this->last_reject_log_ms_ = now;
|
||||
ESP_LOGW(this->tag_, "Rejected %s", text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
link.adopt(std::move(client));
|
||||
ESP_LOGI(this->tag_, "Client connected from %s", text);
|
||||
}
|
||||
|
||||
void TcpListener::dump_config() const {
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
for (size_t i = 0; i < this->allow_.size(); i++) {
|
||||
Ipv4AllowEntry e = this->allow_.entry(i);
|
||||
// Network order is dotted order, and the contiguous mask's popcount is the prefix.
|
||||
const auto *b = reinterpret_cast<const uint8_t *>(&e.addr);
|
||||
ESP_LOGCONFIG(this->tag_, " Allowed IP: %u.%u.%u.%u/%u", b[0], b[1], b[2], b[3],
|
||||
static_cast<unsigned>(__builtin_popcount(e.mask)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::socket
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "headers.h"
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
|
||||
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
#include "ipv4_allow.h"
|
||||
#endif
|
||||
#include "socket.h"
|
||||
#include "tcp_client_link.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
/// The server side of a bridged TCP link: owns the listen socket and the
|
||||
/// allow list, accepts one peer at a time and adopts it into a TcpClientLink,
|
||||
/// sharing that link's retry clock and connect port.
|
||||
class TcpListener {
|
||||
public:
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
void set_allow(const Ipv4AllowEntry *entries, size_t count) { this->allow_.set(entries, count); }
|
||||
#endif
|
||||
|
||||
/// Call from setup(); tag names the log lines.
|
||||
void begin(const char *tag) { this->tag_ = tag; }
|
||||
/// Server state machine; call every loop. may_accept lets the caller hold
|
||||
/// accepts until its own disconnect edge has run.
|
||||
void poll(TcpClientLink &link, bool may_accept) {
|
||||
if (this->listen_ == nullptr) {
|
||||
if (!link.in_backoff()) {
|
||||
this->try_listen_(link);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (may_accept && !link.connected() && this->listen_->ready()) {
|
||||
this->accept_(link);
|
||||
}
|
||||
}
|
||||
void close() { this->listen_.reset(); }
|
||||
/// One config line per allowed network.
|
||||
void dump_config() const;
|
||||
|
||||
protected:
|
||||
void try_listen_(TcpClientLink &link);
|
||||
void accept_(TcpClientLink &link);
|
||||
|
||||
std::unique_ptr<ListenSocket> listen_;
|
||||
const char *tag_{nullptr};
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
uint32_t last_reject_log_ms_{0};
|
||||
Ipv4Allow allow_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::socket
|
||||
|
||||
#endif
|
||||
@@ -73,12 +73,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION]))
|
||||
|
||||
if peak_config := config.get(CONF_PEAK):
|
||||
sens = await sensor.new_sensor(peak_config)
|
||||
cg.add(var.set_peak_sensor(sens))
|
||||
if rms_config := config.get(CONF_RMS):
|
||||
sens = await sensor.new_sensor(rms_config)
|
||||
cg.add(var.set_rms_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_PEAK, var.set_peak_sensor)
|
||||
await sensors(CONF_RMS, var.set_rms_sensor)
|
||||
|
||||
|
||||
SOUND_LEVEL_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
|
||||
@@ -181,9 +181,9 @@ CONFIG_SCHEMA_BASE.add_extra(measurement_timing_check)
|
||||
async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
|
||||
temperature_config = config[CONF_TEMPERATURE]
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
@@ -193,9 +193,8 @@ async def to_code_base(config: ConfigType) -> MockObj:
|
||||
var.set_temperature_sample_rate_config(temperature_config[CONF_SAMPLE_RATE])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
|
||||
pressure_config = config[CONF_PRESSURE]
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_sample_rate_config(pressure_config[CONF_SAMPLE_RATE]))
|
||||
|
||||
|
||||
@@ -48,6 +48,5 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if co2 := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_CO2, var.set_co2_sensor)
|
||||
|
||||
@@ -40,12 +40,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_TAS58XX_ID])
|
||||
if sensor_config := config.get(CONF_HAVE_FAULT):
|
||||
sens = await binary_sensor.new_binary_sensor(sensor_config)
|
||||
cg.add(parent.set_have_fault_binary_sensor(sens))
|
||||
hub = await cg.get_variable(config[CONF_TAS58XX_ID])
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_HAVE_FAULT, hub.set_have_fault_binary_sensor)
|
||||
for key in FAULT_SENSORS:
|
||||
if sensor_config := config.get(key):
|
||||
sens = await binary_sensor.new_binary_sensor(sensor_config)
|
||||
fault = getattr(FaultSensor, f"FAULT_SENSOR_{key.upper()}")
|
||||
cg.add(parent.set_fault_binary_sensor(fault, sens))
|
||||
cg.add(hub.set_fault_binary_sensor(fault, sens))
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor, socket, uart
|
||||
from esphome.components.const import (
|
||||
CONF_ALLOWED_IPS,
|
||||
CONF_CONNECTED,
|
||||
CONF_DATA_BITS,
|
||||
CONF_HOST,
|
||||
CONF_PARITY,
|
||||
CONF_RECONNECT_INTERVAL,
|
||||
CONF_ROLE,
|
||||
CONF_STOP_BITS,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
@@ -24,44 +28,68 @@ MULTI_CONF = True
|
||||
tcp_uart_ns = cg.esphome_ns.namespace("tcp_uart")
|
||||
TcpUart = tcp_uart_ns.class_("TcpUart", uart.UARTComponent, cg.Component)
|
||||
|
||||
CONF_RECONNECT_INTERVAL = "reconnect_interval"
|
||||
CONF_CONNECTED = "connected"
|
||||
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TcpUart),
|
||||
cv.Required(CONF_PORT): cv.port,
|
||||
cv.Optional(CONF_BAUD_RATE, default=9600): cv.int_range(min=1),
|
||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
||||
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
|
||||
uart.UART_PARITY_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||
cv.Optional(
|
||||
CONF_RECONNECT_INTERVAL, default="5s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_CONNECTED): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
cv.typed_schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TcpUart),
|
||||
cv.Required(CONF_HOST): cv.string,
|
||||
cv.Required(CONF_PORT): cv.port,
|
||||
cv.Optional(CONF_BAUD_RATE, default=9600): cv.int_range(min=1),
|
||||
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8),
|
||||
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
|
||||
uart.UART_PARITY_OPTIONS, upper=True
|
||||
"client": BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_HOST): cv.string,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
|
||||
cv.Optional(
|
||||
CONF_RECONNECT_INTERVAL, default="5s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_CONNECTED): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
"server": BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ALLOWED_IPS): socket.IPV4_ALLOW_SCHEMA,
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
socket.consume_sockets(1, "tcp_uart"),
|
||||
},
|
||||
key=CONF_ROLE,
|
||||
default_type="client",
|
||||
lower=True,
|
||||
),
|
||||
socket.consume_role_sockets("tcp_uart"),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
socket.require_tcp_client_link()
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_HOST], config[CONF_PORT])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if config[CONF_ROLE] == "server":
|
||||
socket.require_tcp_listener()
|
||||
cg.add(var.set_server(True))
|
||||
socket.add_ipv4_allow(
|
||||
var.set_allow, config.get(CONF_ALLOWED_IPS), config[CONF_ID]
|
||||
)
|
||||
else:
|
||||
socket.require_tcp_client_link()
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_reconnect_interval(config[CONF_RECONNECT_INTERVAL]))
|
||||
# The socket is not clocked. These only satisfy UARTComponent and a consumer check.
|
||||
cg.add(var.set_baud_rate(config[CONF_BAUD_RATE]))
|
||||
cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
|
||||
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
|
||||
cg.add(var.set_parity(config[CONF_PARITY]))
|
||||
if (host := config.get(CONF_HOST)) is not None:
|
||||
cg.add(var.set_host(host))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_CONNECTED, var.set_connected_sensor)
|
||||
|
||||
@@ -14,6 +14,9 @@ static constexpr uint32_t DROP_LOG_INTERVAL_MS = 5000;
|
||||
|
||||
void TcpUart::setup() {
|
||||
this->link_.begin(TAG);
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.begin(TAG);
|
||||
#endif
|
||||
if (this->connected_sensor_ != nullptr) {
|
||||
this->connected_sensor_->publish_state(false);
|
||||
}
|
||||
@@ -22,18 +25,29 @@ void TcpUart::setup() {
|
||||
void TcpUart::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"TCP UART:\n"
|
||||
" Host: %s:%u\n"
|
||||
" %s: %s:%u\n"
|
||||
" Reconnect Interval: %" PRIu32 "ms",
|
||||
this->link_.host(), this->link_.port(), this->link_.reconnect_interval());
|
||||
this->server_ ? LOG_STR_LITERAL("Listen") : LOG_STR_LITERAL("Host"),
|
||||
this->server_ ? LOG_STR_LITERAL("*") : this->link_.host(), this->link_.port(),
|
||||
this->link_.reconnect_interval());
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.dump_config();
|
||||
#endif
|
||||
LOG_BINARY_SENSOR(" ", "Connected", this->connected_sensor_);
|
||||
}
|
||||
|
||||
void TcpUart::on_shutdown() {
|
||||
this->link_.close();
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TcpUart::sync_link_() {
|
||||
bool up = this->link_.connected();
|
||||
this->link_was_up_ = up;
|
||||
if (!up) {
|
||||
this->rx_start_ = this->rx_end_ = 0;
|
||||
this->tx_len_ = 0;
|
||||
}
|
||||
if (this->connected_sensor_ != nullptr) {
|
||||
this->connected_sensor_->publish_state(up);
|
||||
@@ -63,16 +77,18 @@ void TcpUart::read_socket_() {
|
||||
this->rx_pending_ = static_cast<size_t>(count) == room;
|
||||
}
|
||||
|
||||
void TcpUart::flush_tx_() {
|
||||
ssize_t sent = this->link_.write(this->tx_, this->tx_len_);
|
||||
if (sent > 0) {
|
||||
this->tx_len_ -= static_cast<uint16_t>(sent);
|
||||
std::memmove(this->tx_, this->tx_ + sent, this->tx_len_);
|
||||
}
|
||||
}
|
||||
|
||||
void TcpUart::loop() {
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
if (this->server_) {
|
||||
// link_was_up_ holds the accept until the previous drop's edge has run,
|
||||
// so the sensor and the cleared RX buffer always see the disconnect.
|
||||
this->listener_.poll(this->link_, !this->link_was_up_);
|
||||
} else {
|
||||
this->link_.poll();
|
||||
}
|
||||
#else
|
||||
this->link_.poll();
|
||||
#endif
|
||||
if (this->link_.connected() != this->link_was_up_) {
|
||||
this->sync_link_();
|
||||
}
|
||||
@@ -82,25 +98,20 @@ void TcpUart::loop() {
|
||||
if (this->rx_pending_ || this->link_.ready()) {
|
||||
this->read_socket_();
|
||||
}
|
||||
if (this->tx_len_ != 0) {
|
||||
this->flush_tx_();
|
||||
}
|
||||
this->link_.flush_tx();
|
||||
}
|
||||
|
||||
void TcpUart::write_array(const uint8_t *data, size_t len) {
|
||||
size_t room = this->link_.connected() ? sizeof(this->tx_) - this->tx_len_ : 0;
|
||||
if (len > room) {
|
||||
size_t queued = this->link_.queue(data, len);
|
||||
if (queued < len) {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if (this->last_drop_log_ms_ == 0 || now - this->last_drop_log_ms_ >= DROP_LOG_INTERVAL_MS) {
|
||||
ESP_LOGW(TAG, "%s, dropped %u bytes",
|
||||
this->link_.connected() ? LOG_STR_LITERAL("TX buffer full") : LOG_STR_LITERAL("Not connected"),
|
||||
static_cast<unsigned>(len - room));
|
||||
static_cast<unsigned>(len - queued));
|
||||
this->last_drop_log_ms_ = now;
|
||||
}
|
||||
len = room;
|
||||
}
|
||||
std::memcpy(this->tx_ + this->tx_len_, data, len);
|
||||
this->tx_len_ += static_cast<uint16_t>(len);
|
||||
}
|
||||
|
||||
bool TcpUart::peek_byte(uint8_t *data) {
|
||||
@@ -121,11 +132,13 @@ bool TcpUart::read_array(uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
uart::UARTFlushResult TcpUart::flush() {
|
||||
this->flush_tx_();
|
||||
if (this->tx_len_ == 0) {
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
|
||||
bool emptied = this->link_.flush_tx();
|
||||
if (!this->link_.connected()) {
|
||||
// A down link cannot have delivered anything, whether this flush dropped
|
||||
// it or an earlier loop() write did.
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED;
|
||||
}
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
|
||||
return emptied ? uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS : uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
} // namespace esphome::tcp_uart
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/socket/tcp_client_link.h"
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
#include "esphome/components/socket/tcp_listener.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
@@ -9,22 +12,26 @@
|
||||
|
||||
namespace esphome::tcp_uart {
|
||||
|
||||
/// TCP client presented as a UART. Bytes are copied unchanged.
|
||||
/// TCP client or server presented as a UART. Bytes are copied unchanged.
|
||||
class TcpUart : public uart::UARTComponent, public Component {
|
||||
public:
|
||||
TcpUart(const char *host, uint16_t port) {
|
||||
this->link_.set_host(host);
|
||||
this->link_.set_port(port);
|
||||
this->rx_buffer_size_ = RX_BUFFER_SIZE;
|
||||
}
|
||||
TcpUart() { this->rx_buffer_size_ = RX_BUFFER_SIZE; }
|
||||
|
||||
void set_host(const char *host) { this->link_.set_host(host); }
|
||||
void set_port(uint16_t port) { this->link_.set_port(port); }
|
||||
void set_reconnect_interval(uint32_t ms) { this->link_.set_reconnect_interval(ms); }
|
||||
void set_connected_sensor(binary_sensor::BinarySensor *sensor) { this->connected_sensor_ = sensor; }
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
void set_server(bool server) { this->server_ = server; }
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
void set_allow(const socket::Ipv4AllowEntry *entries, size_t count) { this->listener_.set_allow(entries, count); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void on_shutdown() override { this->link_.close(); }
|
||||
void on_shutdown() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
@@ -32,7 +39,7 @@ class TcpUart : public uart::UARTComponent, public Component {
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override { return static_cast<size_t>(this->rx_end_ - this->rx_start_); }
|
||||
// Same room write_array() grants, so consumers can apply backpressure.
|
||||
size_t available_for_write() override { return this->link_.connected() ? sizeof(this->tx_) - this->tx_len_ : 0; }
|
||||
size_t available_for_write() override { return this->link_.tx_free(); }
|
||||
uart::UARTFlushResult flush() override;
|
||||
bool is_connected() override { return this->link_.connected(); }
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
@@ -43,24 +50,24 @@ class TcpUart : public uart::UARTComponent, public Component {
|
||||
void check_logger_conflict() override {}
|
||||
void sync_link_();
|
||||
void read_socket_();
|
||||
void flush_tx_();
|
||||
|
||||
static constexpr size_t RX_BUFFER_SIZE = 1024;
|
||||
static constexpr size_t TX_BUFFER_SIZE = 1024;
|
||||
|
||||
socket::TcpClientLink link_;
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
socket::TcpListener listener_;
|
||||
#endif
|
||||
binary_sensor::BinarySensor *connected_sensor_{nullptr};
|
||||
uint32_t last_drop_log_ms_{0};
|
||||
uint16_t tx_len_{0};
|
||||
// rx_[rx_start_, rx_end_) holds unread bytes; read_socket_() compacts to the front.
|
||||
uint16_t rx_start_{0};
|
||||
uint16_t rx_end_{0};
|
||||
// The link state loop() saw last; edges clear the buffers and publish the sensor.
|
||||
bool server_{false};
|
||||
// The link state loop() saw last; edges clear rx_ and publish the sensor.
|
||||
bool link_was_up_{false};
|
||||
// A read stopped before EAGAIN. ready() stays false until new data arrives.
|
||||
bool rx_pending_{false};
|
||||
uint8_t rx_[RX_BUFFER_SIZE]{};
|
||||
uint8_t tx_[TX_BUFFER_SIZE]{};
|
||||
};
|
||||
|
||||
} // namespace esphome::tcp_uart
|
||||
|
||||
@@ -46,10 +46,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if raw_pressure_config := config.get(CONF_RAW_PRESSURE):
|
||||
sens = await sensor.new_sensor(raw_pressure_config)
|
||||
cg.add(var.set_raw_pressure_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_RAW_PRESSURE, var.set_raw_pressure_sensor)
|
||||
|
||||
@@ -44,15 +44,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_WIND_SPEED in config:
|
||||
conf = config[CONF_WIND_SPEED]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_wind_speed_sensor(sens))
|
||||
|
||||
if CONF_WIND_DIRECTION_DEGREES in config:
|
||||
conf = config[CONF_WIND_DIRECTION_DEGREES]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_wind_direction_degrees_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_WIND_SPEED, var.set_wind_speed_sensor)
|
||||
await sensors(CONF_WIND_DIRECTION_DEGREES, var.set_wind_direction_degrees_sensor)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor, socket, uart
|
||||
from esphome.components.const import (
|
||||
CONF_ALLOWED_IPS,
|
||||
CONF_CONNECTED,
|
||||
CONF_HOST,
|
||||
CONF_RECONNECT_INTERVAL,
|
||||
CONF_ROLE,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PORT,
|
||||
CONF_UART_ID,
|
||||
DEVICE_CLASS_CONNECTIVITY,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@Bascht74"]
|
||||
DEPENDENCIES = ["network", "uart"]
|
||||
AUTO_LOAD = ["binary_sensor", "socket"]
|
||||
MULTI_CONF = True
|
||||
|
||||
uart_tcp_ns = cg.esphome_ns.namespace("uart_tcp")
|
||||
UartTcp = uart_tcp_ns.class_("UartTcp", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(UartTcp),
|
||||
cv.Required(CONF_UART_ID): cv.use_id(uart.UARTComponent),
|
||||
cv.Required(CONF_PORT): cv.port,
|
||||
cv.Optional(
|
||||
CONF_RECONNECT_INTERVAL, default="5s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_CONNECTED): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_CONNECTIVITY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"client": BASE_SCHEMA.extend({cv.Required(CONF_HOST): cv.string}),
|
||||
"server": BASE_SCHEMA.extend(
|
||||
{cv.Optional(CONF_ALLOWED_IPS): socket.IPV4_ALLOW_SCHEMA}
|
||||
),
|
||||
},
|
||||
key=CONF_ROLE,
|
||||
default_type="client",
|
||||
lower=True,
|
||||
),
|
||||
socket.consume_role_sockets("uart_tcp"),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
if config[CONF_ROLE] == "server":
|
||||
socket.require_tcp_listener()
|
||||
cg.add(var.set_server(True))
|
||||
socket.add_ipv4_allow(
|
||||
var.set_allow, config.get(CONF_ALLOWED_IPS), config[CONF_ID]
|
||||
)
|
||||
else:
|
||||
socket.require_tcp_client_link()
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_reconnect_interval(config[CONF_RECONNECT_INTERVAL]))
|
||||
if (host := config.get(CONF_HOST)) is not None:
|
||||
cg.add(var.set_host(host))
|
||||
binary_sensors = binary_sensor.sub_binary_sensors(config)
|
||||
await binary_sensors(CONF_CONNECTED, var.set_connected_sensor)
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "uart_tcp.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome::uart_tcp {
|
||||
|
||||
static const char *const TAG = "uart_tcp";
|
||||
|
||||
// Bytes per 16 ms loop pass at 10 bits per byte: baud / 10 / 62.5.
|
||||
static constexpr uint32_t BAUD_PACE_DIVISOR = 625;
|
||||
|
||||
void UartTcp::setup() {
|
||||
this->link_.begin(TAG);
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.begin(TAG);
|
||||
#endif
|
||||
if (this->connected_sensor_ != nullptr) {
|
||||
this->connected_sensor_->publish_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UartTcp::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"UART TCP:\n"
|
||||
" %s: %s:%u\n"
|
||||
" Reconnect Interval: %" PRIu32 "ms",
|
||||
this->server_ ? LOG_STR_LITERAL("Listen") : LOG_STR_LITERAL("Host"),
|
||||
this->server_ ? LOG_STR_LITERAL("*") : this->link_.host(), this->link_.port(),
|
||||
this->link_.reconnect_interval());
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.dump_config();
|
||||
#endif
|
||||
LOG_BINARY_SENSOR(" ", "Connected", this->connected_sensor_);
|
||||
}
|
||||
|
||||
void UartTcp::on_shutdown() {
|
||||
this->link_.close();
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
this->listener_.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
void UartTcp::sync_link_() {
|
||||
bool up = this->link_.connected();
|
||||
this->link_was_up_ = up;
|
||||
if (up) {
|
||||
// The driver kept whatever arrived while the link was down.
|
||||
this->discard_uart_();
|
||||
}
|
||||
if (this->connected_sensor_ != nullptr) {
|
||||
this->connected_sensor_->publish_state(up);
|
||||
}
|
||||
}
|
||||
|
||||
void UartTcp::read_socket_() {
|
||||
// A hardware write blocks until the driver takes every byte. Leave what does
|
||||
// not fit in the socket, so TCP flow control throttles the peer.
|
||||
size_t room = this->parent_->available_for_write();
|
||||
if (room == SIZE_MAX) {
|
||||
// Capacity unknown on this platform; pace to one loop pass of UART time
|
||||
// (16 ms at 10 bits per byte) so a blocking write stays short.
|
||||
room = std::max<size_t>(1, this->parent_->get_baud_rate() / BAUD_PACE_DIVISOR);
|
||||
}
|
||||
if (room == 0) {
|
||||
this->rx_pending_ = true;
|
||||
return;
|
||||
}
|
||||
uint8_t tmp[READ_CHUNK];
|
||||
size_t want = std::min(room, sizeof(tmp));
|
||||
ssize_t count = this->link_.read(tmp, want);
|
||||
if (count <= 0) {
|
||||
// A dropped link (-1) is cleaned up by sync_link_() on the next loop.
|
||||
if (count == 0) {
|
||||
this->rx_pending_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->rx_pending_ = static_cast<size_t>(count) == want;
|
||||
this->write_array(tmp, static_cast<size_t>(count));
|
||||
}
|
||||
|
||||
void UartTcp::discard_uart_() {
|
||||
// Drain exactly what was buffered while the link was down; later bytes are live.
|
||||
uint8_t dump[32];
|
||||
size_t left = this->available();
|
||||
while (left != 0) {
|
||||
size_t n = std::min(left, sizeof(dump));
|
||||
if (!this->read_array(dump, n)) {
|
||||
return;
|
||||
}
|
||||
left -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void UartTcp::read_uart_() {
|
||||
size_t want = std::min<size_t>(this->available(), this->link_.tx_free());
|
||||
if (want != 0 && this->read_array(this->link_.tx_tail(), want)) {
|
||||
this->link_.tx_commit(want);
|
||||
}
|
||||
}
|
||||
|
||||
void UartTcp::loop() {
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
if (this->server_) {
|
||||
// link_was_up_ holds the accept until the previous drop's edge has run,
|
||||
// so the sensor and the stale UART discard always see the disconnect.
|
||||
this->listener_.poll(this->link_, !this->link_was_up_);
|
||||
} else {
|
||||
this->link_.poll();
|
||||
}
|
||||
#else
|
||||
this->link_.poll();
|
||||
#endif
|
||||
if (this->link_.connected() != this->link_was_up_) {
|
||||
this->sync_link_();
|
||||
}
|
||||
if (!this->link_was_up_) {
|
||||
return;
|
||||
}
|
||||
if (this->rx_pending_ || this->link_.ready()) {
|
||||
this->read_socket_();
|
||||
}
|
||||
// UART bytes picked up here go out in the same pass.
|
||||
this->read_uart_();
|
||||
this->link_.flush_tx();
|
||||
}
|
||||
|
||||
} // namespace esphome::uart_tcp
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/socket/tcp_client_link.h"
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
#include "esphome/components/socket/tcp_listener.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome::uart_tcp {
|
||||
|
||||
/// Copies raw bytes between one hardware UART and one TCP socket.
|
||||
class UartTcp : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
void set_host(const char *host) { this->link_.set_host(host); }
|
||||
void set_port(uint16_t port) { this->link_.set_port(port); }
|
||||
void set_reconnect_interval(uint32_t ms) { this->link_.set_reconnect_interval(ms); }
|
||||
void set_connected_sensor(binary_sensor::BinarySensor *sensor) { this->connected_sensor_ = sensor; }
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
void set_server(bool server) { this->server_ = server; }
|
||||
#ifdef USE_SOCKET_IPV4_ALLOW
|
||||
void set_allow(const socket::Ipv4AllowEntry *entries, size_t count) { this->listener_.set_allow(entries, count); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
void sync_link_();
|
||||
void read_socket_();
|
||||
void read_uart_();
|
||||
void discard_uart_();
|
||||
|
||||
static constexpr size_t READ_CHUNK = 128;
|
||||
|
||||
socket::TcpClientLink link_;
|
||||
#ifdef USE_SOCKET_TCP_LISTENER
|
||||
socket::TcpListener listener_;
|
||||
#endif
|
||||
binary_sensor::BinarySensor *connected_sensor_{nullptr};
|
||||
bool server_{false};
|
||||
// The link state loop() saw last; edges clear the buffer and publish the sensor.
|
||||
bool link_was_up_{false};
|
||||
// A read stopped before EAGAIN. ready() stays false until new data arrives.
|
||||
bool rx_pending_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::uart_tcp
|
||||
@@ -64,13 +64,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION]))
|
||||
cg.add(var.set_temperature_coefficient(config[CONF_TEMPERATURE_COEFFICIENT]))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_EC in config:
|
||||
sens = await sensor.new_sensor(config[CONF_EC])
|
||||
cg.add(var.set_ec_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_EC, var.set_ec_sensor)
|
||||
|
||||
if CONF_TEMPERATURE_SENSOR in config:
|
||||
sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR])
|
||||
|
||||
@@ -56,13 +56,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_PH in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PH])
|
||||
cg.add(var.set_ph_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_PH, var.set_ph_sensor)
|
||||
|
||||
if CONF_TEMPERATURE_SENSOR in config:
|
||||
sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR])
|
||||
|
||||
@@ -368,6 +368,13 @@ def final_validate(config):
|
||||
"The AP will not be usable for configuration or monitoring. "
|
||||
"Add 'captive_portal:' or 'web_server:' to your configuration."
|
||||
)
|
||||
if "esp32_hosted" in full_config and any(
|
||||
CONF_EAP in net for net in config.get(CONF_NETWORKS, [])
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"WPA2 Enterprise ('eap:') is not supported by the esp32_hosted coprocessor "
|
||||
"firmware ESPHome provides"
|
||||
)
|
||||
|
||||
|
||||
def _consume_wifi_sockets(config: ConfigType) -> ConfigType:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user