mirror of
https://github.com/esphome/esphome.git
synced 2026-10-25 07:25:35 +00:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0094cee7b6 | ||
|
|
35883270fe | ||
|
|
da37e89b9d | ||
|
|
32bbf55f2a | ||
|
|
16759c848f | ||
|
|
244e419f82 | ||
|
|
04f53f0ab4 | ||
|
|
9eef601f99 | ||
|
|
454dbe59db | ||
|
|
7c0fa49b00 | ||
|
|
c9fe7451a6 | ||
|
|
92320ed14e | ||
|
|
601dcce0e4 | ||
|
|
98ab149c24 | ||
|
|
49a8c1d4cc | ||
|
|
39bfdecf75 | ||
|
|
754a960ed1 | ||
|
|
4f1db5b3d1 | ||
|
|
f7a60f5859 | ||
|
|
d0d79d5c54 | ||
|
|
ab2383e7c5 | ||
|
|
def600ff6a | ||
|
|
f1dda7da41 | ||
|
|
b6f101d5a6 | ||
|
|
7c6c0fb4bc | ||
|
|
75ac3f51b5 | ||
|
|
d9e3f2ae3e | ||
|
|
5cc6f6a89d | ||
|
|
23bc50d229 | ||
|
|
472b2281d1 | ||
|
|
b8a0de91db | ||
|
|
2937d81216 | ||
|
|
05a4b256db | ||
|
|
d0427c36eb | ||
|
|
a990231342 |
No files matched your search
@@ -7,7 +7,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_DESCRIPTION, CONF_HOST
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.components.network import DOMAIN as NETWORK_DOMAIN
|
||||
from esphome.components.network import require_ipv4, require_ipv6
|
||||
|
||||
# ENCRYPTION_SCHEMA and validate_encryption_key are re-exported for external
|
||||
# components and downstream consumers that import them from api
|
||||
@@ -28,7 +28,6 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_DELAY,
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_ENCRYPTION,
|
||||
CONF_EVENT,
|
||||
CONF_ID,
|
||||
@@ -52,7 +51,6 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_priority
|
||||
from esphome.cpp_generator import Expression, MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import fnv1_hash
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigFragmentType, ConfigType
|
||||
@@ -333,7 +331,10 @@ def _validate_outgoing_host(value: str) -> IPv4Address | IPv6Address:
|
||||
"give the address without the '%' part"
|
||||
)
|
||||
if (mapped := address.ipv4_mapped) is not None:
|
||||
return mapped
|
||||
address = mapped
|
||||
# The build must carry the host's address family for the dial to work
|
||||
require = require_ipv6 if isinstance(address, IPv6Address) else require_ipv4
|
||||
require("api.outgoing_connection")(value)
|
||||
return address
|
||||
|
||||
|
||||
@@ -479,31 +480,12 @@ def _action_strings(conf: ConfigType, has_metadata: bool) -> list[str | None]:
|
||||
return strings
|
||||
|
||||
|
||||
def _validate_outgoing_host_ipv6(config: ConfigType) -> ConfigType:
|
||||
"""An IPv6 host can never be parsed, so never dialed, without IPv6."""
|
||||
if (
|
||||
(outgoing := config.get(CONF_OUTGOING_CONNECTION)) is None
|
||||
or (host := outgoing.get(CONF_HOST)) is None
|
||||
or host.version != 6
|
||||
):
|
||||
return config
|
||||
network_conf = fv.full_config.get().get(NETWORK_DOMAIN) or {}
|
||||
if not network_conf.get(CONF_ENABLE_IPV6):
|
||||
raise cv.Invalid(
|
||||
"outgoing_connection host is an IPv6 address but IPv6 is not "
|
||||
"enabled; set 'network: enable_ipv6: true'",
|
||||
path=[CONF_OUTGOING_CONNECTION, CONF_HOST],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _validate_wizard(config: ConfigType) -> ConfigType:
|
||||
wizard.final_validate(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
_validate_outgoing_host_ipv6,
|
||||
_validate_wizard,
|
||||
)
|
||||
|
||||
|
||||
@@ -38,12 +38,14 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
const auto *bytes = reinterpret_cast<const uint8_t *>(&addr6->sin6_addr);
|
||||
uint32_t prefix[3];
|
||||
memcpy(prefix, bytes, sizeof(prefix));
|
||||
#if USE_NETWORK_IPV4
|
||||
// A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d
|
||||
if (prefix[0] == 0 && prefix[1] == 0 && prefix[2] == htonl(0xFFFFUL)) {
|
||||
target.family = AF_INET;
|
||||
memcpy(target.addr, bytes + sizeof(prefix), sizeof(struct in_addr));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// A link-local target is only reachable through the interface it came in
|
||||
// on. Device platforms number interfaces from one; a host build can hand
|
||||
// out an index too large to store, and a truncated one dials the wrong
|
||||
@@ -57,6 +59,7 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV4
|
||||
if (family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
@@ -64,6 +67,9 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
target.family = AF_INET;
|
||||
memcpy(target.addr, &addr4->sin_addr, sizeof(addr4->sin_addr));
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -85,6 +91,7 @@ socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *a
|
||||
return sizeof(*addr6);
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV4
|
||||
if (this->saved_.family != AF_INET) {
|
||||
return 0;
|
||||
}
|
||||
@@ -94,6 +101,9 @@ socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *a
|
||||
addr4->sin_port = htons(API_OUTGOING_CONNECTION_PORT);
|
||||
memcpy(&addr4->sin_addr, this->saved_.addr, sizeof(addr4->sin_addr));
|
||||
return sizeof(*addr4);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BUTTON
|
||||
|
||||
#include "bq25186.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome::bq25186 {
|
||||
|
||||
/// Writes one SHIP_RST field when pressed
|
||||
class BQ25186Button final : public button::Button, public Parented<BQ25186Component> {
|
||||
public:
|
||||
BQ25186Button(uint8_t mask, uint8_t value) : mask_(mask), value_(value) {}
|
||||
|
||||
protected:
|
||||
void press_action() override { this->parent_->write_ship_rst(this->mask_, this->value_); }
|
||||
|
||||
const uint8_t mask_;
|
||||
const uint8_t value_;
|
||||
};
|
||||
|
||||
} // namespace esphome::bq25186
|
||||
|
||||
#endif // USE_BUTTON
|
||||
@@ -1,4 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -10,11 +9,10 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, bq25186_ns
|
||||
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID
|
||||
|
||||
DEPENDENCIES = ["bq25186"]
|
||||
|
||||
BQ25186Button = bq25186_ns.class_("BQ25186Button", button.Button)
|
||||
|
||||
# SHIP_RST: bit 7 is the software reset, bits 6:5 the shutdown, ship and hardware reset codes
|
||||
SOFTWARE_RESET_MASK = 0x80
|
||||
@@ -33,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
BQ25186_CHILD_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(key): button.button_schema(
|
||||
BQ25186Button,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=icon,
|
||||
device_class=device_class,
|
||||
@@ -46,7 +44,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
buttons = button.sub_buttons(config, parent=config[CONF_BQ25186_ID])
|
||||
for key, mask, value, _, _ in _BUTTONS:
|
||||
if (conf := config.get(key)) is not None:
|
||||
var = await button.new_button(conf, mask, value)
|
||||
await cg.register_parented(var, config[CONF_BQ25186_ID])
|
||||
await buttons(key, None, apply=f"write_ship_rst({mask:#04x}, {value:#04x})")
|
||||
@@ -15,27 +15,24 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component, dfrobot_sen0609_ns
|
||||
|
||||
DFRobotSEN0609Button = dfrobot_sen0609_ns.class_("DFRobotSEN0609Button", button.Button)
|
||||
ButtonAction = dfrobot_sen0609_ns.enum("ButtonAction")
|
||||
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
DFRobotSEN0609Button,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
@@ -47,11 +44,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DFROBOT_SEN0609_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET,
|
||||
hub.set_factory_reset_button,
|
||||
ButtonAction.BUTTON_FACTORY_RESET,
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_QUERY_PARAMS, hub.set_query_button, ButtonAction.BUTTON_QUERY_PARAMS
|
||||
)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, ButtonAction.BUTTON_RESTART)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="query_params()")
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, apply="restart()")
|
||||
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../dfrobot_sen0609.h"
|
||||
|
||||
namespace esphome::dfrobot_sen0609 {
|
||||
|
||||
class DFRobotSEN0609Button final : public button::Button, public Parented<DFRobotSEN0609Component> {
|
||||
public:
|
||||
explicit DFRobotSEN0609Button(ButtonAction action) : action_(action) {}
|
||||
|
||||
protected:
|
||||
void press_action() override {
|
||||
switch (this->action_) {
|
||||
case BUTTON_RESTART:
|
||||
this->parent_->restart();
|
||||
break;
|
||||
case BUTTON_FACTORY_RESET:
|
||||
this->parent_->factory_reset();
|
||||
break;
|
||||
case BUTTON_QUERY_PARAMS:
|
||||
this->parent_->query_params();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ButtonAction action_;
|
||||
};
|
||||
|
||||
} // namespace esphome::dfrobot_sen0609
|
||||
@@ -62,12 +62,6 @@ enum SwitchSetting : uint8_t {
|
||||
SWITCH_COUNT,
|
||||
};
|
||||
|
||||
enum ButtonAction : uint8_t {
|
||||
BUTTON_RESTART,
|
||||
BUTTON_FACTORY_RESET,
|
||||
BUTTON_QUERY_PARAMS,
|
||||
};
|
||||
|
||||
/// Module parameters; a write or read of each is one command. Pending work runs in this order.
|
||||
enum Param : uint8_t {
|
||||
PARAM_RUN_APP,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import socket
|
||||
from esphome.components import network, socket
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.components.light.types import AddressableLightEffect
|
||||
import esphome.config_validation as cv
|
||||
@@ -38,6 +38,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
network.require_ipv4("e131"),
|
||||
socket.consume_sockets(1, "e131", socket.SocketType.UDP),
|
||||
)
|
||||
|
||||
|
||||
@@ -711,6 +711,15 @@ def set_core_data(config):
|
||||
return config
|
||||
|
||||
|
||||
def _require_ipv4_on_arduino(config: ConfigType) -> ConfigType:
|
||||
"""The Arduino core's IPAddress.cpp does not compile with IPv4 disabled."""
|
||||
if CORE.using_arduino:
|
||||
from esphome.components import network
|
||||
|
||||
network.require_ipv4(PLATFORM_ESP32)(config)
|
||||
return config
|
||||
|
||||
|
||||
def get_esp32_variant(core_obj=None):
|
||||
return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT]
|
||||
|
||||
@@ -2494,6 +2503,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_set_default_framework,
|
||||
_check_versions,
|
||||
set_core_data,
|
||||
_require_ipv4_on_arduino,
|
||||
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any
|
||||
|
||||
from esphome.build_helpers.native import native_backend
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -369,6 +370,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_warn_platformio_toolchain,
|
||||
_validate_native_toolchain,
|
||||
set_core_data,
|
||||
network.require_ipv4(PLATFORM_ESP8266),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_ESPECTRE_ID, ESPectreComponent, espectre_ns
|
||||
from .. import CONF_ESPECTRE_ID, ESPectreComponent
|
||||
|
||||
DEPENDENCIES = ["espectre"]
|
||||
|
||||
RecalibrateButton = espectre_ns.class_("RecalibrateButton", button.Button)
|
||||
|
||||
CONFIG_SCHEMA = button.button_schema(
|
||||
RecalibrateButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)})
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await button.new_button(config)
|
||||
await cg.register_parented(var, config[CONF_ESPECTRE_ID])
|
||||
buttons = button.sub_buttons(config, parent=config[CONF_ESPECTRE_ID])
|
||||
await buttons.create(config, None, apply="recalibrate()")
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESPECTRE
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../espectre.h"
|
||||
|
||||
namespace esphome::espectre {
|
||||
|
||||
class RecalibrateButton final : public button::Button, public Parented<ESPectreComponent> {
|
||||
protected:
|
||||
void press_action() override { this->parent_->recalibrate(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::espectre
|
||||
|
||||
#endif // USE_ESPECTRE
|
||||
@@ -8,6 +8,7 @@ from esphome.components.network import (
|
||||
get_network_priority,
|
||||
get_priority_interfaces_from_full_config,
|
||||
ip_address_literal,
|
||||
require_ipv4,
|
||||
)
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
@@ -22,6 +23,7 @@ from esphome.const import (
|
||||
CONF_DNS2,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_ETHERNET,
|
||||
CONF_GATEWAY,
|
||||
CONF_ID,
|
||||
CONF_INTERRUPT_PIN,
|
||||
@@ -556,6 +558,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
upper=True,
|
||||
),
|
||||
_validate,
|
||||
require_ipv4(CONF_ETHERNET),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from ..climate import CONF_HAIER_ID, HonClimate, haier_ns
|
||||
from ..climate import CONF_HAIER_ID, HonClimate
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
|
||||
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
|
||||
|
||||
|
||||
# Haier buttons
|
||||
@@ -20,19 +18,18 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||
SelfCleaningButton,
|
||||
button.ApplyButton,
|
||||
icon=ICON_SPRAY_BOTTLE,
|
||||
),
|
||||
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
|
||||
SteriCleaningButton,
|
||||
button.ApplyButton,
|
||||
icon=ICON_SPRAY_BOTTLE,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
|
||||
if conf := config.get(button_type):
|
||||
btn = await button.new_button(conf)
|
||||
await cg.register_parented(btn, config[CONF_HAIER_ID])
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
buttons = button.sub_buttons(config, parent=config[CONF_HAIER_ID])
|
||||
await buttons(CONF_SELF_CLEANING, None, apply="start_self_cleaning()")
|
||||
await buttons(CONF_STERI_CLEANING, None, apply="start_steri_cleaning()")
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "self_cleaning.h"
|
||||
|
||||
namespace esphome::haier {
|
||||
|
||||
void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); }
|
||||
|
||||
} // namespace esphome::haier
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../hon_climate.h"
|
||||
|
||||
namespace esphome::haier {
|
||||
|
||||
class SelfCleaningButton final : public button::Button, public Parented<HonClimate> {
|
||||
public:
|
||||
SelfCleaningButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::haier
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "steri_cleaning.h"
|
||||
|
||||
namespace esphome::haier {
|
||||
|
||||
void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); }
|
||||
|
||||
} // namespace esphome::haier
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../hon_climate.h"
|
||||
|
||||
namespace esphome::haier {
|
||||
|
||||
class SteriCleaningButton final : public button::Button, public Parented<HonClimate> {
|
||||
public:
|
||||
SteriCleaningButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::haier
|
||||
@@ -1,10 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ICON_AIR_FILTER
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp, hoermann_hcp_ns
|
||||
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp
|
||||
|
||||
DEPENDENCIES = ["hoermann_hcp"]
|
||||
|
||||
@@ -13,10 +12,6 @@ CONF_VENT = "vent"
|
||||
|
||||
ICON_GARAGE_OPEN_VARIANT = "mdi:garage-open-variant"
|
||||
|
||||
HoermannHcpVentButton = hoermann_hcp_ns.class_("HoermannHcpVentButton", button.Button)
|
||||
HoermannHcpHalfOpenButton = hoermann_hcp_ns.class_(
|
||||
"HoermannHcpHalfOpenButton", button.Button
|
||||
)
|
||||
|
||||
BUTTON_KEYS = (CONF_VENT, CONF_HALF_OPEN)
|
||||
|
||||
@@ -25,10 +20,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(CONF_HOERMANN_HCP_ID): cv.use_id(HoermannHcp),
|
||||
cv.Optional(CONF_VENT): button.button_schema(
|
||||
HoermannHcpVentButton, icon=ICON_AIR_FILTER
|
||||
button.ApplyButton, icon=ICON_AIR_FILTER
|
||||
),
|
||||
cv.Optional(CONF_HALF_OPEN): button.button_schema(
|
||||
HoermannHcpHalfOpenButton, icon=ICON_GARAGE_OPEN_VARIANT
|
||||
button.ApplyButton, icon=ICON_GARAGE_OPEN_VARIANT
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -37,7 +32,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
|
||||
for key in BUTTON_KEYS:
|
||||
if (conf := config.get(key)) is not None:
|
||||
await button.new_button(conf, parent)
|
||||
buttons = button.sub_buttons(config, parent=config[CONF_HOERMANN_HCP_ID])
|
||||
await buttons(CONF_VENT, None, apply="vent_door()")
|
||||
await buttons(CONF_HALF_OPEN, None, apply="half_open_door()")
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../hoermann_hcp.h"
|
||||
|
||||
namespace esphome::hoermann_hcp {
|
||||
|
||||
// The door commands the cover has no equivalent for. A refused command is already reported by the hub and
|
||||
// leaves nothing to correct here, because a button carries no state of its own.
|
||||
class HoermannHcpButton : public button::Button {
|
||||
public:
|
||||
explicit HoermannHcpButton(HoermannHcp *parent) : parent_(parent) {}
|
||||
|
||||
protected:
|
||||
HoermannHcp *const parent_;
|
||||
};
|
||||
|
||||
class HoermannHcpVentButton final : public HoermannHcpButton {
|
||||
public:
|
||||
using HoermannHcpButton::HoermannHcpButton;
|
||||
|
||||
protected:
|
||||
void press_action() override { this->parent_->vent_door(); }
|
||||
};
|
||||
|
||||
class HoermannHcpHalfOpenButton final : public HoermannHcpButton {
|
||||
public:
|
||||
using HoermannHcpButton::HoermannHcpButton;
|
||||
|
||||
protected:
|
||||
void press_action() override { this->parent_->half_open_door(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::hoermann_hcp
|
||||
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MAC_ADDRESS,
|
||||
@@ -44,6 +45,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
# is no PlatformIO backend, so a CLI --toolchain must name this one
|
||||
cv.resolve_toolchain("host", (Toolchain.HOST,), Toolchain.HOST),
|
||||
set_core_data,
|
||||
network.require_ipv4(PLATFORM_HOST),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -107,13 +107,10 @@ def _set_stream_limits(config: ConfigType) -> ConfigType:
|
||||
)(config)
|
||||
return config
|
||||
|
||||
# The original ESP32 cannot lay out sub-16-bit slots that match ESPHome's packed audio, so the smallest
|
||||
# stream it accepts is 16-bit (see start_i2s_driver); the other variants handle 8-bit.
|
||||
min_bits_per_sample = 16 if esp32.get_esp32_variant() == esp32.VARIANT_ESP32 else 8
|
||||
|
||||
# The configured bits per sample sets the I2S slot width, but the speaker narrows wider streams down to it
|
||||
# in place before clocking them out (see start_i2s_driver). Advertise up to 32-bit so those wider streams
|
||||
# are accepted rather than forcing an upstream conversion.
|
||||
# The configured bits per sample sets the I2S slot width, but the speaker converts streams of other widths
|
||||
# before clocking them out (see start_i2s_driver), widening them on the original ESP32. Advertise 8- to 32-bit
|
||||
# so those streams are accepted rather than forcing an upstream conversion.
|
||||
min_bits_per_sample = 8
|
||||
max_bits_per_sample = 32
|
||||
|
||||
if config[CONF_I2S_MODE] == CONF_PRIMARY:
|
||||
@@ -153,10 +150,12 @@ def _validate_esp32_variant(config: ConfigType) -> ConfigType:
|
||||
if variant not in INTERNAL_DAC_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not have an internal DAC")
|
||||
elif variant == esp32.VARIANT_ESP32 and config[CONF_BITS_PER_SAMPLE] == 8:
|
||||
# The original ESP32 I2S peripheral packs each sample into a whole number of 16-bit words, so an
|
||||
# 8-bit slot does not line up with ESPHome's tightly packed audio (see start_i2s_driver). Reject it
|
||||
# at config time rather than emitting corrupted output at runtime.
|
||||
raise cv.Invalid("8-bit audio is not supported on the original ESP32")
|
||||
# An 8-bit slot is rejected on the original ESP32: no known DAC needs one, and it is untested there.
|
||||
# 8-bit streams still work; they are widened to the 16- or 32-bit slot (see start_i2s_driver).
|
||||
raise cv.Invalid(
|
||||
f"{CONF_BITS_PER_SAMPLE} cannot be 8bit on the original ESP32",
|
||||
path=[CONF_BITS_PER_SAMPLE],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -208,8 +208,8 @@ class I2SAudioSpeakerBase : public I2SAudioOut, public speaker::Speaker, public
|
||||
|
||||
audio::AudioStreamInfo current_stream_info_; // Format of the audio in the ring buffer (the I2S input)
|
||||
// Format actually clocked out of the I2S peripheral. Same channel count and sample rate as
|
||||
// current_stream_info_, but the bits per sample may be narrower when the incoming stream is wider than
|
||||
// the speaker's configured slot bit width. Set by start_i2s_driver before the speaker task starts.
|
||||
// current_stream_info_, but the bits per sample may be converted to the speaker's configured slot bit width.
|
||||
// Set by start_i2s_driver before the speaker task starts.
|
||||
audio::AudioStreamInfo output_stream_info_;
|
||||
|
||||
gpio_num_t dout_pin_;
|
||||
|
||||
@@ -38,6 +38,11 @@ static constexpr size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT * 2;
|
||||
// DMA_BUFFER_DURATION_MS, so a multiple of that gives plenty of slack against scheduling jitter
|
||||
// without masking real failures.
|
||||
static constexpr TickType_t WRITE_TIMEOUT_TICKS = pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * (DMA_BUFFERS_COUNT + 1));
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// Stack buffer that widened audio passes through on the original ESP32. Small enough for the speaker task's stack,
|
||||
// large enough that the extra i2s_channel_write calls cost little.
|
||||
static constexpr size_t WIDEN_BUFFER_BYTES = 512;
|
||||
#endif
|
||||
|
||||
// Requested frames per DMA buffer for the given stream, clamped so the byte size stays within the ESP-IDF
|
||||
// maximum DMA descriptor size. This is only the value handed to the channel config: ESP-IDF may still adjust
|
||||
@@ -66,9 +71,8 @@ void I2SAudioSpeaker::dump_config() {
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Communication format: %s", fmt_str);
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
// The width of each I2S slot. It is also the narrowing ceiling: streams wider than this are narrowed to
|
||||
// it. A stream narrower than the slot is left at its own width and clocked into the wider slot, so this
|
||||
// is not necessarily the sample data width (which depends on the incoming stream).
|
||||
// The width of each I2S slot. Wider streams are narrowed to it. Narrower streams are padded into the slot by
|
||||
// the peripheral, except on the original ESP32, where they are widened to it.
|
||||
ESP_LOGCONFIG(TAG, " Slot bit width: %u", (unsigned) static_cast<uint32_t>(this->slot_bit_width_));
|
||||
}
|
||||
}
|
||||
@@ -92,11 +96,14 @@ void I2SAudioSpeaker::run_speaker_task() {
|
||||
const size_t ring_buffer_size =
|
||||
(this->current_stream_info_.ms_to_bytes(ring_buffer_duration) / source_alignment) * source_alignment;
|
||||
|
||||
// Per-frame byte widths and whether the task must narrow the bit depth before writing to the I2S peripheral.
|
||||
// Per-frame byte widths and whether the task must convert the bit depth before writing to the I2S peripheral.
|
||||
const uint8_t channels = this->current_stream_info_.get_channels();
|
||||
const uint8_t input_bytes_per_sample = this->current_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint8_t output_bytes_per_sample = this->output_stream_info_.get_bits_per_sample() / 8;
|
||||
const bool narrowing = input_bytes_per_sample != output_bytes_per_sample;
|
||||
const bool narrowing = input_bytes_per_sample > output_bytes_per_sample;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
const bool widening = input_bytes_per_sample < output_bytes_per_sample;
|
||||
#endif
|
||||
|
||||
// ESP-IDF may allocate smaller (or cache-line-rounded) DMA buffers than dma_buffer_frames() requested: it
|
||||
// clamps each descriptor to the max DMA descriptor size and, on targets that route internal memory through
|
||||
@@ -323,20 +330,26 @@ void I2SAudioSpeaker::run_speaker_task() {
|
||||
const size_t output_bytes = this->output_stream_info_.frames_to_bytes(frames_to_write);
|
||||
|
||||
uint8_t *chunk = audio_source->mutable_data();
|
||||
if (narrowing) {
|
||||
// Narrow the bit depth in place: output exactly aliases input with the same channel count and a
|
||||
// smaller width, which copy_frames handles as a single forward pass. Only the frames about to be
|
||||
// consumed are overwritten, so any unprocessed tail stays intact for the next iteration.
|
||||
esp_audio_libs::pcm_convert::copy_frames(chunk, chunk, input_bytes_per_sample, channels,
|
||||
output_bytes_per_sample, channels, frames_to_write);
|
||||
}
|
||||
this->swap_esp32_mono_samples_(chunk, output_bytes);
|
||||
|
||||
size_t bw = 0;
|
||||
i2s_channel_write(this->tx_handle_, chunk, output_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
if (widening) {
|
||||
bw = this->write_widened_(chunk, frames_to_write);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (narrowing) {
|
||||
// Narrow the bit depth in place: output exactly aliases input with the same channel count and a
|
||||
// smaller width, which copy_frames handles as a single forward pass. Only the frames about to be
|
||||
// consumed are overwritten, so any unprocessed tail stays intact for the next iteration.
|
||||
esp_audio_libs::pcm_convert::copy_frames(chunk, chunk, input_bytes_per_sample, channels,
|
||||
output_bytes_per_sample, channels, frames_to_write);
|
||||
}
|
||||
this->swap_esp32_mono_samples_(chunk, output_bytes);
|
||||
i2s_channel_write(this->tx_handle_, chunk, output_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
}
|
||||
if (bw != output_bytes) {
|
||||
// A short write breaks DMA descriptor alignment for every subsequent event. Drop the chunk rather
|
||||
// than retry it: it was already narrowed in place.
|
||||
// than retry it: it may already have been converted in place.
|
||||
ESP_LOGE(TAG, "Partial DMA write (%u of %u bytes), resyncing DMA lockstep", (unsigned) bw,
|
||||
(unsigned) output_bytes);
|
||||
audio_source->consume(input_bytes);
|
||||
@@ -416,26 +429,26 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver(audio::AudioStreamInfo &audio_stream
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// When the stream is wider than the configured slot bit width, the speaker task narrows each frame in place
|
||||
// before handing it to the I2S peripheral. Compute the output format here so the driver, DMA buffers, and
|
||||
// the task's conversion all agree on the clocked-out width. A stream no wider than the slot width is passed
|
||||
// through unchanged (the slot may still be wider than the data, the existing behavior).
|
||||
// Compute the output width so the driver, DMA buffers, and the task's conversion all agree. A stream wider than
|
||||
// the configured slot is narrowed to it, and a narrower stream keeps its width for the peripheral to pad into the
|
||||
// slot. The original ESP32 cannot clock a slot wider than its data, so there every stream is converted to the
|
||||
// slot's memory width instead.
|
||||
uint8_t output_bits_per_sample = audio_stream_info.get_bits_per_sample();
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
output_bits_per_sample = this->slot_memory_bits_();
|
||||
#else
|
||||
const uint8_t configured_bits = static_cast<uint8_t>(this->slot_bit_width_);
|
||||
if (output_bits_per_sample > configured_bits) {
|
||||
output_bits_per_sample = configured_bits;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
this->output_stream_info_ = audio::AudioStreamInfo(output_bits_per_sample, audio_stream_info.get_channels(),
|
||||
audio_stream_info.get_sample_rate());
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// The original ESP32 I2S peripheral stores each sample in a whole number of 16-bit words (a 24-bit sample
|
||||
// occupies 4 bytes in the DMA buffer, an 8-bit sample 2 bytes), but ESPHome's audio pipeline packs samples
|
||||
// tightly (3 bytes for 24-bit, 1 for 8-bit). The two layouts only line up when the bit depth is a multiple
|
||||
// of 16. The check is on the output width since that is what reaches the peripheral; a wider input is fine
|
||||
// as long as it narrows to a 16- or 32-bit slot.
|
||||
// Only a slot left at AUTO can reach this; every configured slot is converted to 16- or 32-bit samples above
|
||||
if (output_bits_per_sample % 16 != 0) {
|
||||
ESP_LOGE(TAG, "ESP32 supports only 16- or 32-bit output, got %u-bit", (unsigned) output_bits_per_sample);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -487,8 +500,8 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver(audio::AudioStreamInfo &audio_stream
|
||||
|
||||
#ifdef USE_I2S_AUDIO_FULL_DUPLEX
|
||||
audio::AudioStreamInfo I2SAudioSpeaker::full_duplex_stream_info_() const {
|
||||
return audio::AudioStreamInfo(static_cast<uint8_t>(this->slot_bit_width_),
|
||||
this->slot_mode_ == I2S_SLOT_MODE_STEREO ? 2 : 1, this->sample_rate_);
|
||||
return audio::AudioStreamInfo(this->slot_memory_bits_(), this->slot_mode_ == I2S_SLOT_MODE_STEREO ? 2 : 1,
|
||||
this->sample_rate_);
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::build_full_duplex_config(i2s_chan_config_t &chan_cfg, i2s_std_config_t &std_cfg) {
|
||||
@@ -530,8 +543,14 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
slot_mask = I2S_STD_SLOT_BOTH;
|
||||
}
|
||||
|
||||
// Configure the data bit width from the output (post-narrowing) format, which is what is clocked out.
|
||||
const i2s_data_bit_width_t data_bit_width = (i2s_data_bit_width_t) output_stream_info.get_bits_per_sample();
|
||||
// Configure the data bit width from the output (post-conversion) format, which is what is clocked out.
|
||||
i2s_data_bit_width_t data_bit_width = (i2s_data_bit_width_t) output_stream_info.get_bits_per_sample();
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// The original ESP32 clocks data at the slot width; a 24-bit slot sends the top 24 bits of each 32-bit sample
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
data_bit_width = (i2s_data_bit_width_t) this->slot_bit_width_;
|
||||
}
|
||||
#endif
|
||||
i2s_std_slot_config_t slot_cfg;
|
||||
switch (this->i2s_comm_fmt_) {
|
||||
case I2SCommFmt::PCM:
|
||||
@@ -545,23 +564,10 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// There seems to be a bug on the ESP32 (non-variant) platform where setting the slot bit width higher than the
|
||||
// bits per sample causes the audio to play too fast. Setting the ws_width to the configured slot bit width seems
|
||||
// to make it play at the correct speed while sending more bits per slot.
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
uint32_t configured_bit_width = static_cast<uint32_t>(this->slot_bit_width_);
|
||||
slot_cfg.ws_width = configured_bit_width;
|
||||
if (configured_bit_width > 16) {
|
||||
slot_cfg.msb_right = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
slot_cfg.slot_bit_width = this->slot_bit_width_;
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
slot_cfg.ws_width = static_cast<uint32_t>(this->slot_bit_width_);
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32
|
||||
slot_cfg.slot_mask = slot_mask;
|
||||
|
||||
i2s_std_gpio_config_t gpio_cfg = this->parent_->get_pin_config();
|
||||
@@ -574,6 +580,45 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
};
|
||||
}
|
||||
|
||||
uint8_t I2SAudioSpeaker::slot_memory_bits_() const {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// The original ESP32 stores a 24-bit slot's samples in 32-bit words
|
||||
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_24BIT) {
|
||||
return 32;
|
||||
}
|
||||
#endif
|
||||
return static_cast<uint8_t>(this->slot_bit_width_);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
size_t I2SAudioSpeaker::write_widened_(const uint8_t *input, uint32_t frames) {
|
||||
alignas(4) uint8_t widened[WIDEN_BUFFER_BYTES];
|
||||
const uint8_t channels = this->current_stream_info_.get_channels();
|
||||
const uint8_t input_bytes_per_sample = this->current_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint8_t output_bytes_per_sample = this->output_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint32_t frames_per_pass = WIDEN_BUFFER_BYTES / this->output_stream_info_.frames_to_bytes(1);
|
||||
|
||||
size_t bytes_written = 0;
|
||||
while (frames > 0) {
|
||||
const uint32_t pass_frames = std::min(frames, frames_per_pass);
|
||||
esp_audio_libs::pcm_convert::copy_frames(input, widened, input_bytes_per_sample, channels, output_bytes_per_sample,
|
||||
channels, pass_frames);
|
||||
const size_t pass_bytes = this->output_stream_info_.frames_to_bytes(pass_frames);
|
||||
// 8-bit mono widened to 16-bit mono needs the pair swap; passes hold whole pairs since writes are pair-aligned
|
||||
this->swap_esp32_mono_samples_(widened, pass_bytes);
|
||||
size_t bw = 0;
|
||||
i2s_channel_write(this->tx_handle_, widened, pass_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
bytes_written += bw;
|
||||
if (bw != pass_bytes) {
|
||||
break;
|
||||
}
|
||||
input += this->current_stream_info_.frames_to_bytes(pass_frames);
|
||||
frames -= pass_frames;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32
|
||||
|
||||
} // namespace esphome::i2s_audio
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -38,6 +38,17 @@ class I2SAudioSpeaker final : public I2SAudioSpeakerBase {
|
||||
audio::AudioStreamInfo full_duplex_stream_info_() const;
|
||||
#endif
|
||||
|
||||
/// @brief Bits per sample that the configured slot takes in DMA memory.
|
||||
uint8_t slot_memory_bits_() const;
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
/// @brief Widens frames to the output bit depth through a small stack buffer and writes them to the peripheral.
|
||||
/// @param input Frames in the incoming stream format
|
||||
/// @param frames Number of frames to widen and write
|
||||
/// @return Bytes written, which is short of the widened size if a write timed out
|
||||
size_t write_widened_(const uint8_t *input, uint32_t frames);
|
||||
#endif
|
||||
|
||||
/// @brief Frame multiple for DMA buffers and writes; 2 keeps ESP32 mono sample pairs word-aligned.
|
||||
static uint32_t dma_frame_multiple(const audio::AudioStreamInfo &stream_info) {
|
||||
return esp32_mono_swap_needed(stream_info) ? 2 : 1;
|
||||
|
||||
@@ -15,29 +15,25 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
|
||||
|
||||
FactoryResetButton = ld2410_ns.class_("FactoryResetButton", button.Button)
|
||||
QueryButton = ld2410_ns.class_("QueryButton", button.Button)
|
||||
RestartButton = ld2410_ns.class_("RestartButton", button.Button)
|
||||
from .. import CONF_LD2410_ID, LD2410Component
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
|
||||
QueryButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
@@ -47,6 +43,10 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
|
||||
)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="read_all_info()")
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "factory_reset_button.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2410.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
class FactoryResetButton final : public button::Button, public Parented<LD2410Component> {
|
||||
public:
|
||||
FactoryResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "query_button.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
void QueryButton::press_action() { this->parent_->read_all_info(); }
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2410.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
class QueryButton final : public button::Button, public Parented<LD2410Component> {
|
||||
public:
|
||||
QueryButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2410.h"
|
||||
|
||||
namespace esphome::ld2410 {
|
||||
|
||||
class RestartButton final : public button::Button, public Parented<LD2410Component> {
|
||||
public:
|
||||
RestartButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2410
|
||||
@@ -16,38 +16,32 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
|
||||
from .. import CONF_LD2412_ID, LD2412Component
|
||||
|
||||
FactoryResetButton = LD2412_ns.class_("FactoryResetButton", button.Button)
|
||||
QueryButton = LD2412_ns.class_("QueryButton", button.Button)
|
||||
RestartButton = LD2412_ns.class_("RestartButton", button.Button)
|
||||
StartDynamicBackgroundCorrectionButton = LD2412_ns.class_(
|
||||
"StartDynamicBackgroundCorrectionButton", button.Button
|
||||
)
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
|
||||
QueryButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
|
||||
StartDynamicBackgroundCorrectionButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_PULSE,
|
||||
),
|
||||
@@ -57,10 +51,15 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="read_all_info()")
|
||||
await buttons(
|
||||
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
|
||||
hub.set_start_dynamic_background_correction_button,
|
||||
apply="start_dynamic_background_correction()",
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "factory_reset_button.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
class FactoryResetButton final : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
FactoryResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "query_button.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
void QueryButton::press_action() { this->parent_->read_all_info(); }
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
class QueryButton final : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
QueryButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
class RestartButton final : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
RestartButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,9 +0,0 @@
|
||||
#include "start_dynamic_background_correction_button.h"
|
||||
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); }
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome::ld2412 {
|
||||
|
||||
class StartDynamicBackgroundCorrectionButton final : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
StartDynamicBackgroundCorrectionButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2412
|
||||
@@ -12,12 +12,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
|
||||
|
||||
LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button)
|
||||
LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button)
|
||||
LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button)
|
||||
LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button)
|
||||
from .. import CONF_LD2420_ID, LD2420Component
|
||||
|
||||
CONF_APPLY_CONFIG = "apply_config"
|
||||
CONF_REVERT_CONFIG = "revert_config"
|
||||
@@ -27,24 +22,24 @@ CONF_RESTART_MODULE = "restart_module"
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Required(CONF_APPLY_CONFIG): button.button_schema(
|
||||
LD2420ApplyConfigButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_REVERT_CONFIG): button.button_schema(
|
||||
LD2420RevertConfigButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
cv.Optional(CONF_RESTART_MODULE): button.button_schema(
|
||||
LD2420RestartModuleButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
LD2420FactoryResetButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
@@ -54,7 +49,21 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_APPLY_CONFIG, hub.set_apply_config_button)
|
||||
await buttons(CONF_REVERT_CONFIG, hub.set_revert_config_button)
|
||||
await buttons(CONF_RESTART_MODULE, hub.set_restart_module_button)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(
|
||||
CONF_APPLY_CONFIG, hub.set_apply_config_button, apply="apply_config_action()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_REVERT_CONFIG,
|
||||
hub.set_revert_config_button,
|
||||
apply="revert_config_action()",
|
||||
)
|
||||
await buttons(
|
||||
CONF_RESTART_MODULE,
|
||||
hub.set_restart_module_button,
|
||||
apply="restart_module_action()",
|
||||
)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET,
|
||||
hub.set_factory_reset_button,
|
||||
apply="factory_reset_action()",
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "reconfig_buttons.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "ld2420.button");
|
||||
|
||||
namespace esphome::ld2420 {
|
||||
|
||||
void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); }
|
||||
void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); }
|
||||
void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); }
|
||||
void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); }
|
||||
|
||||
} // namespace esphome::ld2420
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2420.h"
|
||||
|
||||
namespace esphome::ld2420 {
|
||||
|
||||
class LD2420ApplyConfigButton final : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420ApplyConfigButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420RevertConfigButton final : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420RevertConfigButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420RestartModuleButton final : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420RestartModuleButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420FactoryResetButton final : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420FactoryResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2420
|
||||
@@ -13,22 +13,19 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
|
||||
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
|
||||
from .. import CONF_LD2450_ID, LD2450Component
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
@@ -39,5 +36,9 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
await buttons(
|
||||
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "factory_reset_button.h"
|
||||
|
||||
namespace esphome::ld2450 {
|
||||
|
||||
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace esphome::ld2450
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome::ld2450 {
|
||||
|
||||
class FactoryResetButton : public button::Button, public Parented<LD2450Component> {
|
||||
public:
|
||||
// User provided, not "= default": `new(p) FactoryResetButton()` would zero-fill .bss that is already zero.
|
||||
FactoryResetButton() {}
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2450
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::ld2450 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
|
||||
|
||||
} // namespace esphome::ld2450
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome::ld2450 {
|
||||
|
||||
class RestartButton : public button::Button, public Parented<LD2450Component> {
|
||||
public:
|
||||
// User provided, not "= default": `new(p) RestartButton()` would zero-fill .bss that is already zero.
|
||||
RestartButton() {}
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::ld2450
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_LIBRETINY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -304,6 +305,15 @@ FRAMEWORK_SCHEMA = cv.All(
|
||||
|
||||
CONFIG_SCHEMA = cv.All(_notify_old_style, cv.require_platformio_toolchain("LibreTiny"))
|
||||
|
||||
|
||||
def _require_ipv4(config: dict) -> dict:
|
||||
"""Declares that LibreTiny builds need IPv4."""
|
||||
# Imported here: logger imports this module, so a module level import would load network for every config
|
||||
from esphome.components import network
|
||||
|
||||
return network.require_ipv4(CONF_LIBRETINY)(config)
|
||||
|
||||
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LTComponent),
|
||||
@@ -318,6 +328,7 @@ BASE_SCHEMA = cv.Schema(
|
||||
BASE_SCHEMA.add_extra(_detect_variant)
|
||||
BASE_SCHEMA.add_extra(cv.require_platformio_toolchain("LibreTiny"))
|
||||
BASE_SCHEMA.add_extra(_update_core_data)
|
||||
BASE_SCHEMA.add_extra(_require_ipv4)
|
||||
|
||||
|
||||
def _configure_lwip(config: dict) -> None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import logger, socket
|
||||
from esphome.components import logger, network, socket
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
idf_version,
|
||||
@@ -35,6 +35,7 @@ from esphome.const import (
|
||||
CONF_KEEPALIVE,
|
||||
CONF_LEVEL,
|
||||
CONF_LOG_TOPIC,
|
||||
CONF_MQTT,
|
||||
CONF_ON_CONNECT,
|
||||
CONF_ON_DISCONNECT,
|
||||
CONF_ON_JSON_MESSAGE,
|
||||
@@ -365,6 +366,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
]
|
||||
),
|
||||
_consume_mqtt_sockets,
|
||||
network.require_ipv4(CONF_MQTT),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
import ipaddress
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -51,6 +53,16 @@ TCPIP_RECVMBOX_OPTIMIZED = 64
|
||||
TCP_SEND_BUFFER_MIN = 2880
|
||||
TCP_SEND_BUFFER_MAX = 65535
|
||||
|
||||
CONF_ENABLE_IPV4 = "enable_ipv4"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkData:
|
||||
require_ipv4: set[str] = field(default_factory=set)
|
||||
require_ipv6: set[str] = field(default_factory=set)
|
||||
request_ipv4_off: set[str] = field(default_factory=set)
|
||||
|
||||
|
||||
# Network priority tracking infrastructure
|
||||
# Components can query this to determine their relative setup priority.
|
||||
# CORE.data[KEY_NETWORK_PRIORITY] is a list of dicts of the form
|
||||
@@ -264,11 +276,35 @@ def has_high_performance_networking() -> bool:
|
||||
return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
|
||||
def validate_ipv6(value: bool) -> bool:
|
||||
if CORE.is_nrf52 and not value:
|
||||
raise cv.Invalid("On nRF52, enable_ipv6 must be true")
|
||||
def _get_data() -> NetworkData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = NetworkData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
return value
|
||||
|
||||
def _ip_request(
|
||||
update: Callable[[NetworkData], None],
|
||||
) -> Callable[[ConfigType], ConfigType]:
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
update(_get_data())
|
||||
return config
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def require_ipv4(name: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Return a validator declaring that component `name` needs IPv4."""
|
||||
return _ip_request(lambda data: data.require_ipv4.add(name))
|
||||
|
||||
|
||||
def require_ipv6(name: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Return a validator declaring that component `name` needs IPv6."""
|
||||
return _ip_request(lambda data: data.require_ipv6.add(name))
|
||||
|
||||
|
||||
def request_ipv4_off(name: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Return a validator asking for IPv4 to default to off, on behalf of `name`."""
|
||||
return _ip_request(lambda data: data.request_ipv4_off.add(name))
|
||||
|
||||
|
||||
def get_network_priority(iface: str) -> float | None:
|
||||
@@ -348,33 +384,30 @@ def _validate_priority_list(value: Any) -> list[dict[str, str]]:
|
||||
return entries
|
||||
|
||||
|
||||
# Shared so _final_validate()'s auto-flip to True enforces the same version floor as
|
||||
# the schema does for a user-typed 'enable_ipv6: true'.
|
||||
_ENABLE_IPV6_FRAMEWORK_VERSION_VALIDATOR = cv.require_framework_version(
|
||||
bk72xx_arduino=cv.Version(1, 7, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp8266_arduino=cv.Version(0, 0, 0),
|
||||
host=cv.Version(0, 0, 0),
|
||||
rp2_arduino=cv.Version(0, 0, 0),
|
||||
nrf52_zephyr=cv.Version(0, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NetworkComponent),
|
||||
cv.SplitDefault(
|
||||
CONF_ENABLE_IPV6,
|
||||
bk72xx=False,
|
||||
esp32=False,
|
||||
esp8266=False,
|
||||
host=False,
|
||||
rp2=False,
|
||||
nrf52=True,
|
||||
): cv.All(
|
||||
cv.Optional(CONF_ENABLE_IPV4): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_IPV6): cv.All(
|
||||
cv.boolean,
|
||||
cv.Any(
|
||||
cv.require_framework_version(
|
||||
bk72xx_arduino=cv.Version(1, 7, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp8266_arduino=cv.Version(0, 0, 0),
|
||||
host=cv.Version(0, 0, 0),
|
||||
rp2_arduino=cv.Version(0, 0, 0),
|
||||
nrf52_zephyr=cv.Version(0, 0, 0),
|
||||
),
|
||||
_ENABLE_IPV6_FRAMEWORK_VERSION_VALIDATOR,
|
||||
cv.boolean_false,
|
||||
),
|
||||
validate_ipv6,
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(
|
||||
@@ -392,13 +425,50 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _resolve_ip_family(
|
||||
config: ConfigType, key: str, family: str, requirers: set[str], default: bool
|
||||
) -> bool:
|
||||
"""Resolve `key` to a bool; return True when a component requires the family."""
|
||||
if not requirers:
|
||||
config.setdefault(key, default)
|
||||
return False
|
||||
if config.get(key) is False:
|
||||
raise cv.Invalid(
|
||||
f"{family} is required by {', '.join(sorted(requirers))}, but "
|
||||
f"'network: {key}: false' explicitly disables it"
|
||||
)
|
||||
config[key] = True
|
||||
return True
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
"""Check that every interface named in 'priority' has a corresponding component block."""
|
||||
full = fv.full_config.get()
|
||||
full_config = fv.full_config.get()
|
||||
data = _get_data()
|
||||
|
||||
if _resolve_ip_family(
|
||||
config, CONF_ENABLE_IPV6, "IPv6", data.require_ipv6, default=False
|
||||
):
|
||||
# Turning IPv6 on must not bypass the version gate cv.Any() enforces.
|
||||
_ENABLE_IPV6_FRAMEWORK_VERSION_VALIDATOR(True)
|
||||
_resolve_ip_family(
|
||||
config,
|
||||
CONF_ENABLE_IPV4,
|
||||
"IPv4",
|
||||
data.require_ipv4,
|
||||
default=not data.request_ipv4_off,
|
||||
)
|
||||
|
||||
if not config[CONF_ENABLE_IPV4] and not config[CONF_ENABLE_IPV6]:
|
||||
raise cv.Invalid(
|
||||
"'enable_ipv4: false' requires 'enable_ipv6: true' -- disabling both "
|
||||
"leaves no IP stack"
|
||||
)
|
||||
|
||||
# Check that every interface named in 'priority' has a corresponding component block.
|
||||
priority_list = config.get(CONF_PRIORITY, [])
|
||||
for entry in priority_list:
|
||||
iface = entry["interface"]
|
||||
if iface not in full:
|
||||
if iface not in full_config:
|
||||
raise cv.Invalid(
|
||||
f"'{iface}' is listed in 'network: priority:' but no '{iface}:' "
|
||||
f"component is configured",
|
||||
@@ -430,7 +500,8 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@coroutine_with_priority(CoroPriority.NETWORK)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_NETWORK")
|
||||
# ESP32 with Arduino uses ESP-IDF network APIs directly, no Arduino Network library needed
|
||||
enable_ipv6 = config[CONF_ENABLE_IPV6]
|
||||
enable_ipv4 = config[CONF_ENABLE_IPV4]
|
||||
|
||||
# Store the user-declared network priority list in CORE.data so that ethernet,
|
||||
# wifi and other network components can query it via get_network_priority()
|
||||
@@ -557,7 +628,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if CORE.is_nrf52:
|
||||
zephyr_add_prj_conf("NETWORKING", True)
|
||||
zephyr_add_prj_conf("NET_IPV6", True)
|
||||
zephyr_add_prj_conf("NET_IPV4", enable_ipv4)
|
||||
zephyr_add_prj_conf("NET_IPV6", enable_ipv6)
|
||||
zephyr_add_prj_conf("NET_TCP", True)
|
||||
zephyr_add_prj_conf("NET_UDP", True)
|
||||
# The nRF Connect SDK replaces mbedTLS with PSA/Oberon crypto and does not provide the
|
||||
@@ -582,28 +654,28 @@ async def to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("NET_TCP_MAX_RECV_WINDOW_SIZE", 2280)
|
||||
zephyr_add_prj_conf("NET_TCP_MAX_SEND_WINDOW_SIZE", 2280)
|
||||
|
||||
if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
|
||||
cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
|
||||
if enable_ipv6:
|
||||
cg.add_define(
|
||||
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
|
||||
)
|
||||
if CORE.is_esp32:
|
||||
if CORE.using_arduino:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True)
|
||||
else:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
|
||||
elif enable_ipv6:
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
|
||||
if CORE.is_bk72xx:
|
||||
cg.add_build_flag("-DCONFIG_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
if CORE.is_rp2:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
|
||||
if enable_ipv6:
|
||||
cg.add_define(
|
||||
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
|
||||
)
|
||||
if CORE.is_esp32:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
|
||||
elif enable_ipv6:
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
|
||||
if CORE.is_bk72xx:
|
||||
cg.add_build_flag("-DCONFIG_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
if CORE.is_rp2:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
|
||||
cg.add_define("USE_NETWORK_IPV4", enable_ipv4)
|
||||
if CORE.is_esp32:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV4", enable_ipv4)
|
||||
|
||||
# Pvariable creation lives in a separate coroutine at NETWORK_SERVICES so it
|
||||
# emits after wifi/ethernet at COMMUNICATION. This keeps compile-time config
|
||||
# (above) separate from C++ object lifecycle and allows wiring in interface
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
#if USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#endif /* USE_ADRDUINO */
|
||||
#endif /* USE_ARDUINO */
|
||||
|
||||
#ifdef USE_HOST
|
||||
#include <arpa/inet.h>
|
||||
#if USE_NETWORK_IPV6
|
||||
using ip4_addr_t = struct in_addr;
|
||||
using ip6_addr_t = struct in6_addr;
|
||||
struct ip_addr_t {
|
||||
struct ip_addr_t { // NOLINT(readability-identifier-naming) mirrors the lwIP name the other platforms use
|
||||
union {
|
||||
struct in6_addr ip6;
|
||||
struct in_addr ip4;
|
||||
@@ -55,9 +55,23 @@ using ip4_addr_t = in_addr;
|
||||
#include <zephyr/net/net_ip.h>
|
||||
#include <zephyr/net/socket.h>
|
||||
#include <zephyr/posix/arpa/inet.h>
|
||||
using ip_addr_t = struct in6_addr;
|
||||
static inline int ipaddr_aton(const char *cp, ip_addr_t *addr) { return inet_pton(AF_INET6, cp, addr) == 1 ? 1 : 0; }
|
||||
#ifndef CONFIG_NET_IPV6
|
||||
#error "Zephyr builds need CONFIG_NET_IPV6"
|
||||
#endif
|
||||
using ip_addr_t = struct in6_addr;
|
||||
static inline int ipaddr_aton(const char *cp, ip_addr_t *addr) {
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
// Dual-stack: a plain IPv4 literal must still parse -- store it as a v4-mapped
|
||||
// IPv6 address (::ffff:a.b.c.d), matching the 4-octet constructor below.
|
||||
struct in_addr addr4;
|
||||
if (inet_pton(AF_INET, cp, &addr4) == 1) {
|
||||
net_ipv6_addr_create_v4_mapped(&addr4, addr);
|
||||
return 1;
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
return inet_pton(AF_INET6, cp, addr) == 1 ? 1 : 0;
|
||||
}
|
||||
#endif /* USE_ZEPHYR */
|
||||
|
||||
#if USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#define arduino_ns Arduino_h
|
||||
@@ -65,16 +79,24 @@ static inline int ipaddr_aton(const char *cp, ip_addr_t *addr) { return inet_pto
|
||||
#define arduino_ns arduino
|
||||
#elif USE_ARDUINO
|
||||
#define arduino_ns
|
||||
#endif
|
||||
#endif /* USE_ESP32_FRAMEWORK_ARDUINO */
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_netif.h>
|
||||
#endif
|
||||
#endif /* USE_ESP32 */
|
||||
|
||||
namespace esphome::network {
|
||||
|
||||
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
|
||||
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
|
||||
static constexpr size_t IP_ADDRESS_BUFFER_SIZE =
|
||||
#if defined(USE_ZEPHYR)
|
||||
// Mainline Zephyr's inet_ntop() (net_addr_ntop() in subsys/net/ip/utils.c) never checks
|
||||
// its size argument, so an undersized buffer overflows instead of failing cleanly. Size
|
||||
// for the worst case: an IPv4-mapped IPv6 address needs up to INET6_ADDRSTRLEN (46).
|
||||
46;
|
||||
#else
|
||||
40;
|
||||
#endif
|
||||
|
||||
/// Lowercase hex digits in IP address string (A-F -> a-f for IPv6 per RFC 5952)
|
||||
inline void lowercase_ip_str(char *buf) {
|
||||
@@ -86,19 +108,58 @@ inline void lowercase_ip_str(char *buf) {
|
||||
|
||||
struct IPAddress {
|
||||
public:
|
||||
#ifdef USE_ZEPHYR
|
||||
#if defined(USE_ZEPHYR)
|
||||
// With CONFIG_NET_IPV4 (dual stack), IPv4 addresses are stored v4-mapped (::ffff:a.b.c.d).
|
||||
IPAddress() { memset(&ip_addr_, 0, sizeof(ip_addr_)); }
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||
struct in_addr addr4{
|
||||
.s_addr = htonl(((uint32_t) first << 24) | ((uint32_t) second << 16) | ((uint32_t) third << 8) | fourth)};
|
||||
net_ipv6_addr_create_v4_mapped(&addr4, &ip_addr_);
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
IPAddress(const std::string &in_address) : ip_addr_{} { ipaddr_aton(in_address.c_str(), &ip_addr_); }
|
||||
IPAddress(const struct in6_addr *other_ip) { ip_addr_ = *other_ip; }
|
||||
IPAddress(const struct sockaddr_in6 *addr) { ip_addr_ = addr->sin6_addr; }
|
||||
|
||||
operator struct in6_addr() const { return ip_addr_; }
|
||||
|
||||
bool is_set() const { return !net_ipv6_is_addr_unspecified(&ip_addr_); }
|
||||
bool is_set() const {
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (net_ipv6_addr_is_v4_mapped(&ip_addr_))
|
||||
return ip_addr_.s6_addr32[3] != 0;
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
return !net_ipv6_is_addr_unspecified(&ip_addr_);
|
||||
}
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
// V4-mapped (::ffff:a.b.c.d) addresses represent IPv4 traffic carried over the AF_INET6 socket
|
||||
// Zephyr always uses when CONFIG_NET_IPV6 is on; anything else is real IPv6.
|
||||
bool is_ip4() const { return this->is_set() && net_ipv6_addr_is_v4_mapped(&ip_addr_); }
|
||||
bool is_ip6() const { return this->is_set() && !net_ipv6_addr_is_v4_mapped(&ip_addr_); }
|
||||
#else
|
||||
bool is_ip4() const { return false; }
|
||||
bool is_ip6() const { return this->is_set(); }
|
||||
bool is_multicast() const { return net_ipv6_is_addr_mcast(&ip_addr_); }
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
bool is_multicast() const {
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (net_ipv6_addr_is_v4_mapped(&ip_addr_)) {
|
||||
struct in_addr addr4{};
|
||||
addr4.s_addr = ip_addr_.s6_addr32[3];
|
||||
return net_ipv4_is_addr_mcast(&addr4);
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
return net_ipv6_is_addr_mcast(&ip_addr_);
|
||||
}
|
||||
char *str_to(char *buf) const {
|
||||
#if defined(CONFIG_NET_IPV4)
|
||||
if (this->is_ip4()) {
|
||||
struct in_addr addr4{};
|
||||
addr4.s_addr = ip_addr_.s6_addr32[3];
|
||||
if (inet_ntop(AF_INET, &addr4, buf, IP_ADDRESS_BUFFER_SIZE) == nullptr)
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
#endif /* CONFIG_NET_IPV4 */
|
||||
if (inet_ntop(AF_INET6, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE) == nullptr)
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
@@ -134,8 +195,8 @@ struct IPAddress {
|
||||
operator ip_addr_t() const { return this->ip_addr_; }
|
||||
bool is_set() const {
|
||||
if (this->ip_addr_.type == IPADDR_TYPE_V6) {
|
||||
static constexpr uint8_t zero[sizeof(struct in6_addr)] = {};
|
||||
return memcmp(this->ip_addr_.u_addr.ip6.s6_addr, zero, sizeof(zero)) != 0;
|
||||
static constexpr uint8_t ZERO[sizeof(struct in6_addr)] = {};
|
||||
return memcmp(this->ip_addr_.u_addr.ip6.s6_addr, ZERO, sizeof(ZERO)) != 0;
|
||||
}
|
||||
return this->ip_addr_.u_addr.ip4.s_addr != 0;
|
||||
}
|
||||
@@ -187,59 +248,72 @@ struct IPAddress {
|
||||
return buf; // IPv4 only, no hex letters to lowercase
|
||||
}
|
||||
#endif // USE_NETWORK_IPV6
|
||||
#else
|
||||
#else // LWIP
|
||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||
#if LWIP_IPV4
|
||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
||||
}
|
||||
#endif /* LWIP_IPV4 */
|
||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_copy(ip_addr_, *other_ip); }
|
||||
IPAddress(const char *in_address) { ipaddr_aton(in_address, &ip_addr_); }
|
||||
IPAddress(const std::string &in_address) { ipaddr_aton(in_address.c_str(), &ip_addr_); }
|
||||
#if LWIP_IPV4
|
||||
IPAddress(ip4_addr_t *other_ip) {
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip4_addr_t));
|
||||
#if LWIP_IPV6
|
||||
ip_addr_.type = IPADDR_TYPE_V4;
|
||||
#endif
|
||||
#endif /* LWIP_IPV6 */
|
||||
}
|
||||
#endif /* LWIP_IPV4 */
|
||||
#if USE_ARDUINO
|
||||
IPAddress(const arduino_ns::IPAddress &other_ip) { ip_addr_set_ip4_u32(&ip_addr_, other_ip); }
|
||||
#endif
|
||||
#endif /* USE_ARDUINO */
|
||||
#if LWIP_IPV6
|
||||
IPAddress(ip6_addr_t *other_ip) {
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip6_addr_t));
|
||||
ip_addr_.type = IPADDR_TYPE_V6;
|
||||
IP_SET_TYPE_VAL(ip_addr_, IPADDR_TYPE_V6);
|
||||
}
|
||||
#endif /* LWIP_IPV6 */
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if LWIP_IPV6
|
||||
IPAddress(esp_ip6_addr_t *other_ip) {
|
||||
memcpy((void *) &ip_addr_.u_addr.ip6, (void *) other_ip, sizeof(esp_ip6_addr_t));
|
||||
ip_addr_.type = IPADDR_TYPE_V6;
|
||||
memcpy((void *) ip_2_ip6(&ip_addr_), (void *) other_ip, sizeof(esp_ip6_addr_t));
|
||||
IP_SET_TYPE_VAL(ip_addr_, IPADDR_TYPE_V6);
|
||||
}
|
||||
#endif /* LWIP_IPV6 */
|
||||
#if LWIP_IPV4
|
||||
IPAddress(esp_ip4_addr_t *other_ip) {
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t));
|
||||
#if LWIP_IPV6
|
||||
ip_addr_.type = IPADDR_TYPE_V4;
|
||||
#endif
|
||||
#endif /* LWIP_IPV6 */
|
||||
}
|
||||
#endif /* LWIP_IPV4 */
|
||||
IPAddress(esp_ip_addr_t *other_ip) {
|
||||
#if LWIP_IPV6
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_));
|
||||
#else
|
||||
#elif LWIP_IPV4
|
||||
memcpy((void *) &ip_addr_, (void *) &other_ip->u_addr.ip4, sizeof(ip_addr_));
|
||||
#endif
|
||||
#endif /* LWIP_IPV6 */
|
||||
}
|
||||
operator esp_ip_addr_t() const {
|
||||
esp_ip_addr_t tmp;
|
||||
esp_ip_addr_t tmp{};
|
||||
#if LWIP_IPV6
|
||||
memcpy((void *) &tmp, (void *) &ip_addr_, sizeof(ip_addr_));
|
||||
#else
|
||||
#if !LWIP_IPV4
|
||||
tmp.type = IPADDR_TYPE_V6;
|
||||
#endif /* !LWIP_IPV4 */
|
||||
#elif LWIP_IPV4
|
||||
memcpy((void *) &tmp.u_addr.ip4, (void *) &ip_addr_, sizeof(ip_addr_));
|
||||
tmp.type = IPADDR_TYPE_V4;
|
||||
#else
|
||||
#error "Neither LWIP_IPV4 nor LWIP_IPV6 is enabled"
|
||||
#endif /* LWIP_IPV6 */
|
||||
return tmp;
|
||||
}
|
||||
#if LWIP_IPV4
|
||||
operator esp_ip4_addr_t() const {
|
||||
esp_ip4_addr_t tmp;
|
||||
#if LWIP_IPV6
|
||||
@@ -249,16 +323,17 @@ struct IPAddress {
|
||||
#endif /* LWIP_IPV6 */
|
||||
return tmp;
|
||||
}
|
||||
#endif /* LWIP_IPV4 */
|
||||
#endif /* USE_ESP32 */
|
||||
|
||||
operator ip_addr_t() const { return ip_addr_; }
|
||||
#if LWIP_IPV6
|
||||
#if LWIP_IPV6 && LWIP_IPV4
|
||||
operator ip4_addr_t() const { return *ip_2_ip4(&ip_addr_); }
|
||||
#endif /* LWIP_IPV6 */
|
||||
#endif /* LWIP_IPV6 && LWIP_IPV4 */
|
||||
|
||||
#if USE_ARDUINO
|
||||
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
|
||||
#endif
|
||||
#endif /* USE_ARDUINO */
|
||||
|
||||
bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
@@ -273,17 +348,19 @@ struct IPAddress {
|
||||
}
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
#if LWIP_IPV4
|
||||
IPAddress &operator+=(uint8_t increase) {
|
||||
if (IP_IS_V4(&ip_addr_)) {
|
||||
#if LWIP_IPV6
|
||||
(((u8_t *) (&ip_addr_.u_addr.ip4))[3]) += increase;
|
||||
#else
|
||||
(((u8_t *) (&ip_addr_.addr))[3]) += increase;
|
||||
#endif /* LWIP_IPV6 */
|
||||
#endif /* LWIP_IPV6 */
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
#endif /* LWIP_IPV4 */
|
||||
#endif // LWIP
|
||||
|
||||
protected:
|
||||
ip_addr_t ip_addr_;
|
||||
@@ -292,4 +369,4 @@ struct IPAddress {
|
||||
using IPAddresses = std::array<IPAddress, 5>;
|
||||
|
||||
} // namespace esphome::network
|
||||
#endif
|
||||
#endif /* NETWORK */
|
||||
@@ -11,6 +11,7 @@ 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 import network
|
||||
from esphome.components.zephyr import (
|
||||
Section,
|
||||
add_extra_script,
|
||||
@@ -322,6 +323,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
_default_self_reset_pin,
|
||||
_resolve_toolchain,
|
||||
set_framework,
|
||||
network.require_ipv6(PLATFORM_NRF52),
|
||||
network.request_ipv4_off(PLATFORM_NRF52),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from esphome.components.esp32 import (
|
||||
require_vfs_select,
|
||||
)
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
from esphome.components.network import DOMAIN as NETWORK_DOMAIN, add_use_address
|
||||
from esphome.components.network import add_use_address, request_ipv4_off, require_ipv6
|
||||
from esphome.components.nrf52.framework import include_west_project
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
@@ -40,7 +40,6 @@ from esphome.const import (
|
||||
CONF_AP,
|
||||
CONF_BAUD_RATE,
|
||||
CONF_CHANNEL,
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_HARDWARE_UART,
|
||||
@@ -50,6 +49,7 @@ from esphome.const import (
|
||||
CONF_LOGGER,
|
||||
CONF_NETWORKS,
|
||||
CONF_NUMBER,
|
||||
CONF_OPENTHREAD,
|
||||
CONF_OUTPUT_POWER,
|
||||
CONF_RESET_PIN,
|
||||
CONF_RX_PIN,
|
||||
@@ -448,17 +448,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
_validate_platform,
|
||||
_validate,
|
||||
_require_vfs,
|
||||
require_ipv6(CONF_OPENTHREAD),
|
||||
request_ipv4_off(CONF_OPENTHREAD),
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
full_config = fv.full_config.get()
|
||||
network_config = full_config.get(NETWORK_DOMAIN, {})
|
||||
if not network_config.get(CONF_ENABLE_IPV6, False):
|
||||
raise cv.Invalid(
|
||||
"OpenThread requires IPv6 to be enabled in the network component. "
|
||||
"Please set `enable_ipv6: true` in the `network` configuration."
|
||||
)
|
||||
|
||||
wifi_config = full_config.get(CONF_WIFI)
|
||||
if CONF_BORDER_ROUTER not in config:
|
||||
|
||||
@@ -7,6 +7,7 @@ import subprocess
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -318,6 +319,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_detect_variant,
|
||||
cv.require_platformio_toolchain("RP2"),
|
||||
set_core_data,
|
||||
network.require_ipv4(PLATFORM_RP2),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -9,23 +9,20 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns
|
||||
|
||||
RestartButton = mr24hpc1_ns.class_("RestartButton", button.Button)
|
||||
CustomSetEndButton = mr24hpc1_ns.class_("CustomSetEndButton", button.Button)
|
||||
from .. import CONF_MR24HPC1_ID, MR24HPC1Component
|
||||
|
||||
CONF_CUSTOM_SET_END = "custom_set_end"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_SET_END): button.button_schema(
|
||||
CustomSetEndButton,
|
||||
button.ApplyButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon="mdi:cog",
|
||||
),
|
||||
@@ -35,5 +32,9 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_MR24HPC1_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(CONF_CUSTOM_SET_END, hub.set_custom_set_end_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, apply="set_restart()")
|
||||
await buttons(
|
||||
CONF_CUSTOM_SET_END,
|
||||
hub.set_custom_set_end_button,
|
||||
apply="set_custom_end_mode()",
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "custom_mode_end_button.h"
|
||||
|
||||
namespace esphome::seeed_mr24hpc1 {
|
||||
|
||||
void CustomSetEndButton::press_action() { this->parent_->set_custom_end_mode(); }
|
||||
|
||||
} // namespace esphome::seeed_mr24hpc1
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../seeed_mr24hpc1.h"
|
||||
|
||||
namespace esphome::seeed_mr24hpc1 {
|
||||
|
||||
class CustomSetEndButton final : public button::Button, public Parented<MR24HPC1Component> {
|
||||
public:
|
||||
CustomSetEndButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::seeed_mr24hpc1
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::seeed_mr24hpc1 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->set_restart(); }
|
||||
|
||||
} // namespace esphome::seeed_mr24hpc1
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../seeed_mr24hpc1.h"
|
||||
|
||||
namespace esphome::seeed_mr24hpc1 {
|
||||
|
||||
class RestartButton final : public button::Button, public Parented<MR24HPC1Component> {
|
||||
public:
|
||||
RestartButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::seeed_mr24hpc1
|
||||
@@ -10,24 +10,22 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns
|
||||
from .. import CONF_MR60FDA2_ID, MR60FDA2Component
|
||||
|
||||
DEPENDENCIES = ["seeed_mr60fda2"]
|
||||
|
||||
GetRadarParametersButton = mr60fda2_ns.class_("GetRadarParametersButton", button.Button)
|
||||
ResetRadarButton = mr60fda2_ns.class_("ResetRadarButton", button.Button)
|
||||
|
||||
CONF_GET_RADAR_PARAMETERS = "get_radar_parameters"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component),
|
||||
cv.Optional(CONF_GET_RADAR_PARAMETERS): button.button_schema(
|
||||
GetRadarParametersButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_UPDATE,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
ResetRadarButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
@@ -37,5 +35,11 @@ CONFIG_SCHEMA = {
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_MR60FDA2_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_GET_RADAR_PARAMETERS, hub.set_get_radar_parameters_button)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||
await buttons(
|
||||
CONF_GET_RADAR_PARAMETERS,
|
||||
hub.set_get_radar_parameters_button,
|
||||
apply="get_radar_parameters()",
|
||||
)
|
||||
await buttons(
|
||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "get_radar_parameters_button.h"
|
||||
|
||||
namespace esphome::seeed_mr60fda2 {
|
||||
|
||||
void GetRadarParametersButton::press_action() { this->parent_->get_radar_parameters(); }
|
||||
|
||||
} // namespace esphome::seeed_mr60fda2
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../seeed_mr60fda2.h"
|
||||
|
||||
namespace esphome::seeed_mr60fda2 {
|
||||
|
||||
class GetRadarParametersButton final : public button::Button, public Parented<MR60FDA2Component> {
|
||||
public:
|
||||
GetRadarParametersButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::seeed_mr60fda2
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "reset_radar_button.h"
|
||||
|
||||
namespace esphome::seeed_mr60fda2 {
|
||||
|
||||
void ResetRadarButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace esphome::seeed_mr60fda2
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../seeed_mr60fda2.h"
|
||||
|
||||
namespace esphome::seeed_mr60fda2 {
|
||||
|
||||
class ResetRadarButton final : public button::Button, public Parented<MR60FDA2Component> {
|
||||
public:
|
||||
ResetRadarButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::seeed_mr60fda2
|
||||
@@ -211,9 +211,15 @@ def require_tcp_listener() -> None:
|
||||
|
||||
|
||||
def consume_role_sockets(component: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Socket accounting for a role keyed client or server schema."""
|
||||
"""Socket accounting for a role keyed client or server schema.
|
||||
|
||||
Also requires IPv4, since the TCP link and listener helpers are IPv4 only.
|
||||
"""
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
from esphome.components import network
|
||||
|
||||
network.require_ipv4(component)(config)
|
||||
if config[CONF_ROLE] == "server":
|
||||
consume_sockets(1, component, SocketType.TCP_LISTEN)(config)
|
||||
return consume_sockets(1, component)(config)
|
||||
|
||||
@@ -204,6 +204,11 @@ using socklen_t = uint32_t;
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
|
||||
|
||||
// Build tests compile socket without network, which normally defines this
|
||||
#ifndef USE_NETWORK_IPV4
|
||||
#define USE_NETWORK_IPV4 1
|
||||
#endif
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
// Maximum length for formatted socket address string (IP address without port)
|
||||
|
||||
@@ -42,54 +42,55 @@ bool socket_ready_fd(int /*fd*/, bool /*loop_monitored*/) { return true; }
|
||||
#endif
|
||||
|
||||
// Platform-specific inet_ntop wrappers
|
||||
#if USE_NETWORK_IPV4
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
inet_ntoa_r(*reinterpret_cast<const struct in_addr *>(addr), buf, size);
|
||||
return buf;
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
inet6_ntoa_r(*reinterpret_cast<const ip6_addr_t *>(addr), buf, size);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
// LWIP sockets (LibreTiny, ESP32 Arduino)
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return lwip_inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return lwip_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Zephyr BSD sockets — use Zephyr native address formatting via POSIX-subset wrappers.
|
||||
// <zephyr/net/socket.h> is already included transitively through <sys/socket.h>.
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return zsock_inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
// IPv6 is always enabled on nRF52 (config validation enforces enable_ipv6=True),
|
||||
// but the guard is retained for consistency with other platform blocks.
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return zsock_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// BSD sockets (host, ESP32-IDF)
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV4 */
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
inet6_ntoa_r(*reinterpret_cast<const ip6_addr_t *>(addr), buf, size);
|
||||
return buf;
|
||||
}
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return lwip_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#elif defined(USE_ZEPHYR)
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return zsock_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#else
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out) {
|
||||
#if USE_NETWORK_IPV4
|
||||
if (addr->sa_family == AF_INET) {
|
||||
*out = reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr;
|
||||
return true;
|
||||
@@ -105,23 +106,31 @@ bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
(void) addr;
|
||||
(void) out;
|
||||
#endif /* USE_NETWORK_IPV4 */
|
||||
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 USE_NETWORK_IPV4
|
||||
if (addr_ptr->sa_family == AF_INET && len >= sizeof(const struct sockaddr_in)) {
|
||||
const auto *addr = reinterpret_cast<const struct sockaddr_in *>(addr_ptr);
|
||||
if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV4 */
|
||||
#if USE_NETWORK_IPV6
|
||||
else if (addr_ptr->sa_family == AF_INET6 && len >= sizeof(sockaddr_in6)) {
|
||||
if (addr_ptr->sa_family == AF_INET6 && len >= sizeof(sockaddr_in6)) {
|
||||
const auto *addr = reinterpret_cast<const struct sockaddr_in6 *>(addr_ptr);
|
||||
#if USE_NETWORK_IPV4
|
||||
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());
|
||||
#endif /* USE_NETWORK_IPV4 */
|
||||
if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
@@ -235,6 +244,10 @@ template<typename F> static bool foreach_eligible_ipv6_if(F &&callback) {
|
||||
|
||||
bool join_multicast_group(UDPSocket *sock, const char *ip_address, uint32_t *if_index_out) {
|
||||
if (strchr(ip_address, ':') == nullptr) {
|
||||
#if !USE_NETWORK_IPV4
|
||||
errno = EAFNOSUPPORT;
|
||||
return false;
|
||||
#else
|
||||
struct in_addr ipv4mc{};
|
||||
#ifdef USE_ZEPHYR
|
||||
if (zsock_inet_pton(AF_INET, ip_address, &ipv4mc) != 1) {
|
||||
@@ -262,6 +275,7 @@ bool join_multicast_group(UDPSocket *sock, const char *ip_address, uint32_t *if_
|
||||
*if_index_out = 0;
|
||||
}
|
||||
return true;
|
||||
#endif // USE_NETWORK_IPV4
|
||||
}
|
||||
#ifdef USE_SOCKET_IPV6
|
||||
struct ipv6_mreq imreq6{};
|
||||
@@ -373,7 +387,7 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
|
||||
#else
|
||||
// Use LWIP-specific functions
|
||||
ip6_addr_t ip6;
|
||||
if (inet6_aton(ip_address, &ip6) == 0) {
|
||||
if (inet6_aton(ip_address, &ip6) != 1) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
@@ -382,6 +396,7 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
|
||||
return sizeof(sockaddr_in6);
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
#if USE_NETWORK_IPV4
|
||||
if (addrlen < sizeof(sockaddr_in)) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
@@ -404,6 +419,11 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
|
||||
#endif
|
||||
server->sin_port = htons(port);
|
||||
return sizeof(sockaddr_in);
|
||||
#else
|
||||
memset(addr, 0, addrlen);
|
||||
errno = EAFNOSUPPORT;
|
||||
return 0;
|
||||
#endif /* USE_NETWORK_IPV4 */
|
||||
}
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor, sensor, socket
|
||||
from esphome.components import binary_sensor, network, sensor, socket
|
||||
from esphome.components.const import CONF_HOST
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -48,6 +48,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("10s")),
|
||||
network.require_ipv4("statsd"),
|
||||
socket.consume_sockets(1, "statsd", socket.SocketType.UDP),
|
||||
)
|
||||
|
||||
|
||||
@@ -14,23 +14,21 @@ from esphome.const import (
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_TFLUNA_ID, FACTORY_DEFAULT_ADDRESS, TFLunaComponent, tfluna_ns
|
||||
from .. import CONF_TFLUNA_ID, FACTORY_DEFAULT_ADDRESS, TFLunaComponent
|
||||
|
||||
DEPENDENCIES = ["tfluna"]
|
||||
|
||||
ResetButton = tfluna_ns.class_("ResetButton", button.Button)
|
||||
RestartButton = tfluna_ns.class_("RestartButton", button.Button)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_TFLUNA_ID): cv.use_id(TFLunaComponent),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
ResetButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
button.ApplyButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
@@ -63,5 +61,5 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_TFLUNA_ID])
|
||||
buttons = button.sub_buttons(config, parent=hub)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_reset_button)
|
||||
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||
await buttons(CONF_FACTORY_RESET, hub.set_reset_button, apply="factory_reset()")
|
||||
await buttons(CONF_RESTART, hub.set_restart_button, apply="restart()")
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "reset_button.h"
|
||||
|
||||
namespace esphome::tfluna {
|
||||
|
||||
void ResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace esphome::tfluna
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../tfluna.h"
|
||||
|
||||
namespace esphome::tfluna {
|
||||
|
||||
class ResetButton : public button::Button, public Parented<TFLuna> {
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::tfluna
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome::tfluna {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart(); }
|
||||
|
||||
} // namespace esphome::tfluna
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../tfluna.h"
|
||||
|
||||
namespace esphome::tfluna {
|
||||
|
||||
class RestartButton : public button::Button, public Parented<TFLuna> {
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::tfluna
|
||||
@@ -4,8 +4,8 @@ from typing import Any
|
||||
from esphome import automation
|
||||
from esphome.automation import Trigger
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
from esphome.components.const import CONF_ON_RECEIVE
|
||||
from esphome.components.network import DOMAIN as NETWORK_DOMAIN
|
||||
from esphome.components.packet_transport import (
|
||||
CONF_BINARY_SENSORS,
|
||||
CONF_ENCRYPTION,
|
||||
@@ -15,13 +15,7 @@ from esphome.components.packet_transport import (
|
||||
CONF_SENSORS,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_ID,
|
||||
CONF_PORT,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
@@ -106,7 +100,6 @@ def _final_validate(config: ConfigType) -> None:
|
||||
from esphome.components import socket
|
||||
|
||||
full_config = fv.full_config.get()
|
||||
ipv6_enabled = full_config.get(NETWORK_DOMAIN, {}).get(CONF_ENABLE_IPV6, False)
|
||||
raw_lwip = (
|
||||
full_config.get(socket.DOMAIN, {}).get(socket.CONF_IMPLEMENTATION)
|
||||
== socket.IMPLEMENTATION_LWIP_TCP
|
||||
@@ -122,11 +115,6 @@ def _final_validate(config: ConfigType) -> None:
|
||||
raise cv.Invalid(
|
||||
f"IPv6 address '{addr}' is not supported on this platform", [key]
|
||||
)
|
||||
if not ipv6_enabled:
|
||||
raise cv.Invalid(
|
||||
f"IPv6 address '{addr}' requires 'enable_ipv6: true' in the 'network' component",
|
||||
[key],
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@@ -136,6 +124,12 @@ def _ipv6_addresses(config: ConfigType) -> list[IPv6Address]:
|
||||
return [addr for addr in config[CONF_ADDRESSES] if addr.version == 6]
|
||||
|
||||
|
||||
def _require_ipv6_for_ipv6_addresses(config: ConfigType) -> ConfigType:
|
||||
if _ipv6_addresses(config) or config[CONF_LISTEN_ADDRESS].version == 6:
|
||||
network.require_ipv6("udp")(config)
|
||||
return config
|
||||
|
||||
|
||||
def _has_ipv4_address(config: ConfigType) -> bool:
|
||||
return any(addr.version == 4 for addr in config[CONF_ADDRESSES])
|
||||
|
||||
@@ -178,6 +172,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(RELOCATED),
|
||||
_consume_udp_sockets,
|
||||
network.require_ipv4("udp"),
|
||||
_require_ipv6_for_ipv6_addresses,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
from esphome import automation, core
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import media_player, micro_wake_word, microphone, speaker
|
||||
from esphome.components import (
|
||||
media_player,
|
||||
micro_wake_word,
|
||||
microphone,
|
||||
network,
|
||||
speaker,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -169,6 +175,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.key_requires((CONF_ON_TTS_STREAM_START, CONF_ON_TTS_STREAM_END), CONF_SPEAKER),
|
||||
network.require_ipv4("voice_assistant"),
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button, socket
|
||||
from esphome.components import button, network, socket
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
@@ -22,6 +22,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
),
|
||||
network.require_ipv4("wake_on_lan"),
|
||||
socket.consume_sockets(1, "wake_on_lan", socket.SocketType.UDP),
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from esphome.components.network import (
|
||||
has_high_performance_wifi,
|
||||
ip_address_literal,
|
||||
require_high_performance_wifi,
|
||||
require_ipv4,
|
||||
)
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
@@ -702,6 +703,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
_apply_min_auth_mode_default,
|
||||
_validate,
|
||||
_report_provisioning_credentials,
|
||||
require_ipv4(DOMAIN),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import time
|
||||
from esphome.components import network, time
|
||||
from esphome.components.esp32 import CORE, add_idf_sdkconfig_option
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -92,6 +92,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
PLATFORM_LN882X,
|
||||
]
|
||||
),
|
||||
network.require_ipv4("wireguard"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -241,6 +241,13 @@
|
||||
#define NETWORK_ACTIVE_INTERFACE_LISTENER_COUNT 1
|
||||
#define USE_NETWORK
|
||||
#define USE_NETWORK_DEFAULT_ROUTE
|
||||
#define USE_NETWORK_IPV4 true
|
||||
// lwIP is built without IPv6 on ESP8266, RP2 and LibreTiny
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
#define USE_NETWORK_IPV6 true
|
||||
#else
|
||||
#define USE_NETWORK_IPV6 false
|
||||
#endif
|
||||
#define USE_NETWORK_PRIMARY_INTERFACE_WIFI
|
||||
#define USE_NEXTION_COMMAND_SPACING
|
||||
#define USE_NEXTION_CONF_START_UP_PAGE
|
||||
|
||||
@@ -51,6 +51,11 @@ def _ninja_logs(skip_bootloader: bool) -> list[str]:
|
||||
|
||||
|
||||
BOOTLOADER_BYPRODUCT = re.compile(r"(^|/build/)bootloader/")
|
||||
# The build step of any CMake ExternalProject. One with BUILD_ALWAYS (ESP-IDF's
|
||||
# ulp_embed_binary) re-runs on every build, and ninja re-records all of that
|
||||
# edge's byproducts with the new end time even though the files are untouched.
|
||||
# Real work on them still changes the hashed firmware.
|
||||
EXTERNAL_PROJECT_BUILD_STAMP = re.compile(r"-stamp/[^/]+-build$")
|
||||
MACRO_CHANGED = (
|
||||
"IDF changed __build_process_project_includes; update "
|
||||
"IDF_BOOTLOADER_OVERRIDE in esphome/build_gen/espidf.py"
|
||||
@@ -96,10 +101,24 @@ def _ninja_mtimes(build_path: Path, logs: list[str]) -> dict[tuple[str, str], st
|
||||
for name in logs:
|
||||
log = build_path / name
|
||||
lines = log.read_text(encoding="utf-8").splitlines() if log.is_file() else []
|
||||
for fields in (line.split("\t") for line in lines if not line.startswith("#")):
|
||||
if len(fields) >= 4 and (
|
||||
name != TOP_NINJA_LOG or not BOOTLOADER_BYPRODUCT.search(fields[3])
|
||||
):
|
||||
rows = [
|
||||
fields
|
||||
for fields in (
|
||||
line.split("\t") for line in lines if not line.startswith("#")
|
||||
)
|
||||
if len(fields) >= 5
|
||||
]
|
||||
# Every output of an edge shares its command hash, so the stamp names
|
||||
# the whole external project group.
|
||||
external = {
|
||||
fields[4]
|
||||
for fields in rows
|
||||
if EXTERNAL_PROJECT_BUILD_STAMP.search(fields[3])
|
||||
}
|
||||
for fields in rows:
|
||||
if fields[4] in external:
|
||||
continue
|
||||
if name != TOP_NINJA_LOG or not BOOTLOADER_BYPRODUCT.search(fields[3]):
|
||||
mtimes[name, fields[3]] = fields[2]
|
||||
return mtimes
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import network
|
||||
from esphome.components.api import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32
|
||||
import esphome.config_validation as cv
|
||||
@@ -134,22 +135,20 @@ def test_outgoing_connection_folds_v4_mapped_host(
|
||||
assert FINAL_VALIDATE_SCHEMA(config) is not None
|
||||
|
||||
|
||||
def test_outgoing_connection_ipv6_host_requires_ipv6(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
@pytest.mark.parametrize(
|
||||
("host", "required", "other"),
|
||||
[
|
||||
("192.168.1.2", "require_ipv4", "require_ipv6"),
|
||||
("::ffff:192.168.1.2", "require_ipv4", "require_ipv6"),
|
||||
("fd00::1", "require_ipv6", "require_ipv4"),
|
||||
],
|
||||
)
|
||||
def test_outgoing_connection_host_requires_its_address_family(
|
||||
set_core_config: SetCoreConfigCallable, host: str, required: str, other: str
|
||||
) -> None:
|
||||
"""The host's family is registered with network, which resolves enable_ipv4/6 later."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF, platform_data=ESP32_PLATFORM_DATA)
|
||||
config = CONFIG_SCHEMA(_api_config({"host": "fd00::1"}))
|
||||
with pytest.raises(cv.Invalid, match="IPv6 is not"):
|
||||
FINAL_VALIDATE_SCHEMA(config)
|
||||
|
||||
|
||||
def test_outgoing_connection_ipv6_host_passes_with_ipv6_enabled(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data=ESP32_PLATFORM_DATA,
|
||||
full_config={"network": {"enable_ipv6": True}},
|
||||
)
|
||||
config = CONFIG_SCHEMA(_api_config({"host": "fd00::1"}))
|
||||
assert FINAL_VALIDATE_SCHEMA(config) is not None
|
||||
CONFIG_SCHEMA(_api_config({"host": host}))
|
||||
data = network._get_data()
|
||||
assert "api.outgoing_connection" in getattr(data, required)
|
||||
assert "api.outgoing_connection" not in getattr(data, other)
|
||||
@@ -55,6 +55,7 @@ def reset_core() -> Generator[None]:
|
||||
"""Reset CORE after each test."""
|
||||
yield
|
||||
CORE.reset()
|
||||
CORE.testing_mode = False # reset() leaves it, so a test enabling it would leak
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
esphome:
|
||||
name: platformtest
|
||||
|
||||
bk72xx:
|
||||
board: generic-bk7252
|
||||
@@ -0,0 +1,11 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
enable_ipv4: false
|
||||
@@ -0,0 +1,12 @@
|
||||
esphome:
|
||||
name: ardtest
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
api:
|
||||
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: platformtest
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
@@ -0,0 +1,5 @@
|
||||
esphome:
|
||||
name: platformtest
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
@@ -0,0 +1,9 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
enable_ipv4: false
|
||||
@@ -0,0 +1,5 @@
|
||||
esphome:
|
||||
name: platformtest
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
@@ -0,0 +1,9 @@
|
||||
esphome:
|
||||
name: hosttest
|
||||
|
||||
host:
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
api:
|
||||
@@ -0,0 +1,12 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32c6
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
udp:
|
||||
|
||||
openthread:
|
||||
tlv: 0e080000000000010000000300001035060004001fffe00208e227ac6a7f24052f0708fdb753eb517cb4d3051062b2442a928d9ea3b947a1618fc4085a030f4f70656e5468726561642d393837330102987304105330d857354330133c05e1fd7ae81a910c0402a0f7f8
|
||||
@@ -0,0 +1,14 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
wifi:
|
||||
ssid: "test_ssid"
|
||||
password: "test_password"
|
||||
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv6: false
|
||||
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -0,0 +1,11 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
enable_ipv4: false
|
||||
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: nrf52test
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
network:
|
||||
|
||||
api:
|
||||
@@ -0,0 +1,6 @@
|
||||
esphome:
|
||||
name: platformtest
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
@@ -0,0 +1,13 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
variant: esp32c6
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
network:
|
||||
enable_ipv4: true
|
||||
|
||||
openthread:
|
||||
tlv: 0e080000000000010000000300001035060004001fffe00208e227ac6a7f24052f0708fdb753eb517cb4d3051062b2442a928d9ea3b947a1618fc4085a030f4f70656e5468726561642d393837330102987304105330d857354330133c05e1fd7ae81a910c0402a0f7f8
|
||||
Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user