1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-26 12:43:48 +00:00

Merge branch 'light_effects' into integration

This commit is contained in:
J. Nick Koston
2025-10-22 12:32:33 -10:00
12 changed files with 65 additions and 22 deletions

View File

@@ -178,12 +178,9 @@ void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
bool LightState::supports_effects() { return !this->effects_.empty(); }
const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
void LightState::add_effects(const std::vector<LightEffect *> &effects) {
void LightState::add_effects(const std::initializer_list<LightEffect *> &effects) {
// Called once from Python codegen during setup with all effects from YAML config
this->effects_.init(effects.size());
for (auto *effect : effects) {
this->effects_.push_back(effect);
}
this->effects_ = effects;
}
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }

View File

@@ -163,7 +163,7 @@ class LightState : public EntityBase, public Component {
const FixedVector<LightEffect *> &get_effects() const;
/// Add effects for this light state.
void add_effects(const std::vector<LightEffect *> &effects);
void add_effects(const std::initializer_list<LightEffect *> &effects);
/// Get the total number of effects available for this light.
size_t get_effect_count() const { return this->effects_.size(); }

View File

@@ -99,7 +99,11 @@ const std::string &get_use_address() {
return wifi::global_wifi_component->get_use_address();
#endif
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI)
#ifdef USE_OPENTHREAD
return openthread::global_openthread_component->get_use_address();
#endif
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD)
// Fallback when no network component is defined (e.g., host platform)
static const std::string empty;
return empty;

View File

@@ -8,8 +8,10 @@ from esphome.components.esp32 import (
)
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
from .const import (
CONF_DEVICE_TYPE,
@@ -108,6 +110,12 @@ _CONNECTION_SCHEMA = cv.Schema(
)
def _validate(config: ConfigType) -> ConfigType:
if CONF_USE_ADDRESS not in config:
config[CONF_USE_ADDRESS] = f"{CORE.name}.local"
return config
def _require_vfs_select(config):
"""Register VFS select requirement during config validation."""
# OpenThread uses esp_vfs_eventfd which requires VFS select support
@@ -126,11 +134,13 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
cv.Optional(CONF_TLV): cv.string_strict,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
}
).extend(_CONNECTION_SCHEMA),
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
cv.only_with_esp_idf,
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
_validate,
_require_vfs_select,
)
@@ -158,6 +168,7 @@ async def to_code(config):
enable_mdns_storage()
ot = cg.new_Pvariable(config[CONF_ID])
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
await cg.register_component(ot, config)
srp = cg.new_Pvariable(config[CONF_SRP_ID])

View File

@@ -252,6 +252,12 @@ void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
}
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
const std::string &OpenThreadComponent::get_use_address() const { return this->use_address_; }
void OpenThreadComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
} // namespace openthread
} // namespace esphome

View File

@@ -33,11 +33,15 @@ class OpenThreadComponent : public Component {
void on_factory_reset(std::function<void()> callback);
void defer_factory_reset_external_callback();
const std::string &get_use_address() const;
void set_use_address(const std::string &use_address);
protected:
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
bool teardown_started_{false};
bool teardown_complete_{false};
std::function<void()> factory_reset_external_callback_;
std::string use_address_;
};
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -213,10 +213,14 @@ def _validate(config):
if CONF_EAP in config:
network[CONF_EAP] = config.pop(CONF_EAP)
if CONF_NETWORKS in config:
# In testing mode, merged component tests may have both ssid and networks
# Just use the networks list and ignore the single ssid
if not CORE.testing_mode:
raise cv.Invalid(
"You cannot use the 'ssid:' option together with 'networks:'. Please "
"copy your network into the 'networks:' key"
)
else:
config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network)
if (CONF_NETWORKS not in config) and (CONF_AP not in config):

View File

@@ -636,11 +636,9 @@ class EsphomeCore:
if self.config is None:
raise ValueError("Config has not been loaded yet")
if CONF_WIFI in self.config:
return self.config[CONF_WIFI][CONF_USE_ADDRESS]
if CONF_ETHERNET in self.config:
return self.config[CONF_ETHERNET][CONF_USE_ADDRESS]
for network_type in (CONF_WIFI, CONF_ETHERNET, CONF_OPENTHREAD):
if network_type in self.config:
return self.config[network_type][CONF_USE_ADDRESS]
if CONF_OPENTHREAD in self.config:
return f"{self.name}.local"

View File

@@ -118,8 +118,13 @@ def create_intelligent_batches(
continue
# Get signature from any platform (they should all have the same buses)
# Components not in component_buses were filtered out by has_test_files check
comp_platforms = component_buses[component]
# Components not in component_buses may only have variant-specific tests
comp_platforms = component_buses.get(component)
if not comp_platforms:
# Component has tests but no analyzable base config - treat as no buses
signature_groups[(ALL_PLATFORMS, NO_BUSES_SIGNATURE)].append(component)
continue
for platform, buses in comp_platforms.items():
if buses:
signature = create_grouping_signature({platform: buses}, platform)

View File

@@ -0,0 +1,11 @@
fan:
- platform: template
id: test_fan
name: "Test Fan"
preset_modes:
- Eco
- Sleep
- Turbo
has_oscillating: true
has_direction: true
speed_count: 3

View File

@@ -0,0 +1 @@
<<: !include common.yaml

View File

@@ -571,9 +571,11 @@ class TestEsphomeCore:
assert target.address == "4.3.2.1"
def test_address__openthread(self, target):
target.name = "test-device"
target.config = {}
target.config[const.CONF_OPENTHREAD] = {}
target.config[const.CONF_OPENTHREAD] = {
const.CONF_USE_ADDRESS: "test-device.local"
}
target.name = "test-device"
assert target.address == "test-device.local"