Merge remote-tracking branch 'origin/dev' into socket-lwip-raw-udp

This commit is contained in:
J. Nick Koston
2026-10-02 14:50:55 -05:00
161 changed files with 2266 additions and 792 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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
+4 -10
View File
@@ -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(
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -6
View File
@@ -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)
+3 -7
View File
@@ -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)
+8 -13
View File
@@ -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)
+3 -7
View File
@@ -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)
+5 -6
View File
@@ -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]))
+5 -6
View File
@@ -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
+5 -6
View File
@@ -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]))
+2 -3
View File
@@ -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(
+2 -3
View File
@@ -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)
+4
View File
@@ -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"
+4 -9
View File
@@ -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(
+3 -6
View File
@@ -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]))
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+2 -3
View File
@@ -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
+4 -11
View File
@@ -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)
+3 -7
View File
@@ -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)
+10 -16
View File
@@ -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
+2 -3
View File
@@ -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]))
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -8
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -6
View File
@@ -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)
+6 -11
View File
@@ -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))
+3 -4
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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(
+2 -3
View File
@@ -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)
+3 -9
View File
@@ -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)
+3 -7
View File
@@ -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(
+5 -12
View File
@@ -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])
+3 -8
View File
@@ -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]))
+4 -9
View File
@@ -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)
+1 -1
View File
@@ -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"
+4 -10
View File
@@ -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)
+8 -13
View File
@@ -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)
+8 -13
View File
@@ -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)
+3 -7
View File
@@ -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)
+5 -10
View File
@@ -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))
+3 -7
View File
@@ -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)
+7 -5
View File
@@ -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(
+1 -1
View File
@@ -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"]),
):
+15 -1
View File
@@ -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"
+2 -3
View File
@@ -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)
+4 -11
View File
@@ -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)
+2 -3
View File
@@ -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(
+2 -3
View File
@@ -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(
+3 -6
View File
@@ -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)
+2 -3
View File
@@ -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}"):
+2 -3
View File
@@ -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}"):
+3 -7
View File
@@ -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)
+2 -3
View File
@@ -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(
+3 -7
View File
@@ -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)
+3 -7
View File
@@ -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)
+3 -4
View File
@@ -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)
+3 -4
View File
@@ -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)
+56 -1
View File
@@ -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",
}
)
+63
View File
@@ -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
+22 -22
View File
@@ -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());
}
+4
View File
@@ -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);
+23 -1
View File
@@ -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();
}
+25 -3
View File
@@ -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
+60
View File
@@ -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
+3 -6
View File
@@ -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(
+5 -6
View File
@@ -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]))
+2 -3
View File
@@ -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)
+4 -5
View File
@@ -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))
+50 -22
View File
@@ -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)
+37 -24
View File
@@ -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
+20 -13
View File
@@ -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
+3 -7
View File
@@ -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)
+3 -9
View File
@@ -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))
+77
View File
@@ -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)
+132
View File
@@ -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
+56
View File
@@ -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
+3 -7
View File
@@ -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])
+3 -7
View File
@@ -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])
+7
View File
@@ -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