mirror of
https://github.com/esphome/esphome.git
synced 2025-10-26 20:53:50 +00:00
Merge branch 'light_effects' into integration
This commit is contained in:
@@ -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; }
|
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
|
||||||
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
||||||
const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
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
|
// Called once from Python codegen during setup with all effects from YAML config
|
||||||
this->effects_.init(effects.size());
|
this->effects_ = effects;
|
||||||
for (auto *effect : effects) {
|
|
||||||
this->effects_.push_back(effect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class LightState : public EntityBase, public Component {
|
|||||||
const FixedVector<LightEffect *> &get_effects() const;
|
const FixedVector<LightEffect *> &get_effects() const;
|
||||||
|
|
||||||
/// Add effects for this light state.
|
/// 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.
|
/// Get the total number of effects available for this light.
|
||||||
size_t get_effect_count() const { return this->effects_.size(); }
|
size_t get_effect_count() const { return this->effects_.size(); }
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ const std::string &get_use_address() {
|
|||||||
return wifi::global_wifi_component->get_use_address();
|
return wifi::global_wifi_component->get_use_address();
|
||||||
#endif
|
#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)
|
// Fallback when no network component is defined (e.g., host platform)
|
||||||
static const std::string empty;
|
static const std::string empty;
|
||||||
return empty;
|
return empty;
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ from esphome.components.esp32 import (
|
|||||||
)
|
)
|
||||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||||
import esphome.config_validation as cv
|
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
|
import esphome.final_validate as fv
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_DEVICE_TYPE,
|
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):
|
def _require_vfs_select(config):
|
||||||
"""Register VFS select requirement during config validation."""
|
"""Register VFS select requirement during config validation."""
|
||||||
# OpenThread uses esp_vfs_eventfd which requires VFS select support
|
# 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_FORCE_DATASET): cv.boolean,
|
||||||
cv.Optional(CONF_TLV): cv.string_strict,
|
cv.Optional(CONF_TLV): cv.string_strict,
|
||||||
|
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||||
}
|
}
|
||||||
).extend(_CONNECTION_SCHEMA),
|
).extend(_CONNECTION_SCHEMA),
|
||||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||||
cv.only_with_esp_idf,
|
cv.only_with_esp_idf,
|
||||||
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
||||||
|
_validate,
|
||||||
_require_vfs_select,
|
_require_vfs_select,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -158,6 +168,7 @@ async def to_code(config):
|
|||||||
enable_mdns_storage()
|
enable_mdns_storage()
|
||||||
|
|
||||||
ot = cg.new_Pvariable(config[CONF_ID])
|
ot = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
||||||
await cg.register_component(ot, config)
|
await cg.register_component(ot, config)
|
||||||
|
|
||||||
srp = cg.new_Pvariable(config[CONF_SRP_ID])
|
srp = cg.new_Pvariable(config[CONF_SRP_ID])
|
||||||
|
|||||||
@@ -252,6 +252,12 @@ void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
|
|||||||
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
|
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 openthread
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,15 @@ class OpenThreadComponent : public Component {
|
|||||||
void on_factory_reset(std::function<void()> callback);
|
void on_factory_reset(std::function<void()> callback);
|
||||||
void defer_factory_reset_external_callback();
|
void defer_factory_reset_external_callback();
|
||||||
|
|
||||||
|
const std::string &get_use_address() const;
|
||||||
|
void set_use_address(const std::string &use_address);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
||||||
bool teardown_started_{false};
|
bool teardown_started_{false};
|
||||||
bool teardown_complete_{false};
|
bool teardown_complete_{false};
|
||||||
std::function<void()> factory_reset_external_callback_;
|
std::function<void()> factory_reset_external_callback_;
|
||||||
|
std::string use_address_;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|||||||
@@ -213,10 +213,14 @@ def _validate(config):
|
|||||||
if CONF_EAP in config:
|
if CONF_EAP in config:
|
||||||
network[CONF_EAP] = config.pop(CONF_EAP)
|
network[CONF_EAP] = config.pop(CONF_EAP)
|
||||||
if CONF_NETWORKS in config:
|
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(
|
raise cv.Invalid(
|
||||||
"You cannot use the 'ssid:' option together with 'networks:'. Please "
|
"You cannot use the 'ssid:' option together with 'networks:'. Please "
|
||||||
"copy your network into the 'networks:' key"
|
"copy your network into the 'networks:' key"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network)
|
config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network)
|
||||||
|
|
||||||
if (CONF_NETWORKS not in config) and (CONF_AP not in config):
|
if (CONF_NETWORKS not in config) and (CONF_AP not in config):
|
||||||
|
|||||||
@@ -636,11 +636,9 @@ class EsphomeCore:
|
|||||||
if self.config is None:
|
if self.config is None:
|
||||||
raise ValueError("Config has not been loaded yet")
|
raise ValueError("Config has not been loaded yet")
|
||||||
|
|
||||||
if CONF_WIFI in self.config:
|
for network_type in (CONF_WIFI, CONF_ETHERNET, CONF_OPENTHREAD):
|
||||||
return self.config[CONF_WIFI][CONF_USE_ADDRESS]
|
if network_type in self.config:
|
||||||
|
return self.config[network_type][CONF_USE_ADDRESS]
|
||||||
if CONF_ETHERNET in self.config:
|
|
||||||
return self.config[CONF_ETHERNET][CONF_USE_ADDRESS]
|
|
||||||
|
|
||||||
if CONF_OPENTHREAD in self.config:
|
if CONF_OPENTHREAD in self.config:
|
||||||
return f"{self.name}.local"
|
return f"{self.name}.local"
|
||||||
|
|||||||
@@ -118,8 +118,13 @@ def create_intelligent_batches(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Get signature from any platform (they should all have the same buses)
|
# 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
|
# Components not in component_buses may only have variant-specific tests
|
||||||
comp_platforms = component_buses[component]
|
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():
|
for platform, buses in comp_platforms.items():
|
||||||
if buses:
|
if buses:
|
||||||
signature = create_grouping_signature({platform: buses}, platform)
|
signature = create_grouping_signature({platform: buses}, platform)
|
||||||
|
|||||||
11
tests/components/fan/common.yaml
Normal file
11
tests/components/fan/common.yaml
Normal 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
|
||||||
1
tests/components/fan/test.esp8266-ard.yaml
Normal file
1
tests/components/fan/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common.yaml
|
||||||
@@ -571,9 +571,11 @@ class TestEsphomeCore:
|
|||||||
assert target.address == "4.3.2.1"
|
assert target.address == "4.3.2.1"
|
||||||
|
|
||||||
def test_address__openthread(self, target):
|
def test_address__openthread(self, target):
|
||||||
target.name = "test-device"
|
|
||||||
target.config = {}
|
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"
|
assert target.address == "test-device.local"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user