Merge remote-tracking branch 'upstream/dev' into ci/lazy-import-offenders

# Conflicts:
#	esphome/loader.py
This commit is contained in:
J. Nick Koston
2026-04-28 15:38:40 -05:00
39 changed files with 341 additions and 208 deletions
+3 -3
View File
@@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
run: echo key="${{ hashFiles('requirements.txt', 'requirements_dev.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
@@ -58,7 +58,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_test.txt pre-commit
pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt pre-commit
pip install -e .
pylint:
@@ -369,7 +369,7 @@ jobs:
echo "binary=$BINARY" >> $GITHUB_OUTPUT
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@658a901452bb54c799643e060733b7afe9121b8d # v4.14.0
uses: CodSpeedHQ/action@c381be0bfd20e844fb45594f6aa182ffcd94545c # v4.15.0
with:
run: ${{ steps.build.outputs.binary }}
mode: simulation
+1
View File
@@ -146,5 +146,6 @@ sdkconfig.*
/components
/managed_components
/dependencies.lock
api-docs/
+27 -27
View File
@@ -78,43 +78,43 @@ void BME680Component::setup() {
}
// Read calibration
uint8_t cal1[25];
if (!this->read_bytes(BME680_REGISTER_COEFF1, cal1, 25)) {
uint8_t coeff1[25];
if (!this->read_bytes(BME680_REGISTER_COEFF1, coeff1, 25)) {
this->mark_failed();
return;
}
uint8_t cal2[16];
if (!this->read_bytes(BME680_REGISTER_COEFF2, cal2, 16)) {
uint8_t coeff2[16];
if (!this->read_bytes(BME680_REGISTER_COEFF2, coeff2, 16)) {
this->mark_failed();
return;
}
this->calibration_.t1 = cal2[9] << 8 | cal2[8];
this->calibration_.t2 = cal1[2] << 8 | cal1[1];
this->calibration_.t3 = cal1[3];
this->calibration_.t1 = coeff2[9] << 8 | coeff2[8];
this->calibration_.t2 = coeff1[2] << 8 | coeff1[1];
this->calibration_.t3 = coeff1[3];
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4;
this->calibration_.h3 = cal2[3];
this->calibration_.h4 = cal2[4];
this->calibration_.h5 = cal2[5];
this->calibration_.h6 = cal2[6];
this->calibration_.h7 = cal2[7];
this->calibration_.h1 = coeff2[2] << 4 | (coeff2[1] & 0x0F);
this->calibration_.h2 = coeff2[0] << 4 | coeff2[1] >> 4;
this->calibration_.h3 = coeff2[3];
this->calibration_.h4 = coeff2[4];
this->calibration_.h5 = coeff2[5];
this->calibration_.h6 = coeff2[6];
this->calibration_.h7 = coeff2[7];
this->calibration_.p1 = cal1[6] << 8 | cal1[5];
this->calibration_.p2 = cal1[8] << 8 | cal1[7];
this->calibration_.p3 = cal1[9];
this->calibration_.p4 = cal1[12] << 8 | cal1[11];
this->calibration_.p5 = cal1[14] << 8 | cal1[13];
this->calibration_.p6 = cal1[16];
this->calibration_.p7 = cal1[15];
this->calibration_.p8 = cal1[20] << 8 | cal1[19];
this->calibration_.p9 = cal1[22] << 8 | cal1[21];
this->calibration_.p10 = cal1[23];
this->calibration_.p1 = coeff1[6] << 8 | coeff1[5];
this->calibration_.p2 = coeff1[8] << 8 | coeff1[7];
this->calibration_.p3 = coeff1[9];
this->calibration_.p4 = coeff1[12] << 8 | coeff1[11];
this->calibration_.p5 = coeff1[14] << 8 | coeff1[13];
this->calibration_.p6 = coeff1[16];
this->calibration_.p7 = coeff1[15];
this->calibration_.p8 = coeff1[20] << 8 | coeff1[19];
this->calibration_.p9 = coeff1[22] << 8 | coeff1[21];
this->calibration_.p10 = coeff1[23];
this->calibration_.gh1 = cal2[14];
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
this->calibration_.gh3 = cal2[15];
this->calibration_.gh1 = coeff2[14];
this->calibration_.gh2 = coeff2[12] << 8 | coeff2[13];
this->calibration_.gh3 = coeff2[15];
uint8_t temp_var = 0;
if (!this->read_byte(0x02, &temp_var)) {
+8 -5
View File
@@ -193,11 +193,14 @@ def _validate_ex1_wakeup_mode(value):
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
if not CORE.is_bk72xx:
return value
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
if CORE.is_bk72xx:
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
elif CORE.using_zephyr:
max_duration = core.TimePeriod(days=49)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 49 days on Zephyr")
return value
@@ -9,18 +9,11 @@ static const char *const TAG = "deep_sleep";
// 5 seconds for deep sleep to ensure clean disconnect from Home Assistant
static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000;
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<DeepSleepComponent *> global_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void DeepSleepComponent::setup() {
#ifdef USE_ZEPHYR
k_sem_init(&this->wakeup_sem_, 0, 1);
#endif
global_has_deep_sleep = true;
this->schedule_sleep_();
// It can be used from another thread for waking up the device.
// It should be called as last item in setup.
global_deep_sleep.store(this);
}
void DeepSleepComponent::schedule_sleep_() {
@@ -4,8 +4,6 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include <atomic>
#ifdef USE_ESP32
#include <esp_sleep.h>
#endif
@@ -15,10 +13,6 @@
#include "esphome/core/time.h"
#endif
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
#endif
#include <cinttypes>
namespace esphome {
@@ -125,9 +119,6 @@ class DeepSleepComponent : public Component {
void prevent_deep_sleep();
void allow_deep_sleep();
#ifdef USE_ZEPHYR
void wakeup();
#endif
protected:
// Returns nullopt if no run duration is set. Otherwise, returns the run
@@ -167,9 +158,6 @@ class DeepSleepComponent : public Component {
optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false};
bool prevent_{false};
#ifdef USE_ZEPHYR
k_sem wakeup_sem_;
#endif
};
extern bool global_has_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -256,8 +244,5 @@ template<typename... Ts> class AllowDeepSleepAction : public Action<Ts...>, publ
void play(const Ts &...x) override { this->parent_->allow_deep_sleep(); }
};
extern std::atomic<DeepSleepComponent *>
global_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace deep_sleep
} // namespace esphome
@@ -1,17 +1,13 @@
#include "deep_sleep_component.h"
#ifdef USE_ZEPHYR
#include "esphome/core/log.h"
#include "esphome/core/wake.h"
#include <zephyr/sys/poweroff.h>
#include <zephyr/kernel.h>
#include <zephyr/stats/stats.h>
#include <zephyr/pm/pm.h>
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
void DeepSleepComponent::wakeup() { k_sem_give(&this->wakeup_sem_); }
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {}
@@ -19,9 +15,8 @@ void DeepSleepComponent::dump_config_platform_() {}
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
void DeepSleepComponent::deep_sleep_() {
k_timeout_t sleep_duration = K_FOREVER;
if (this->sleep_duration_.has_value()) {
sleep_duration = K_USEC(*this->sleep_duration_);
esphome::internal::wakeable_delay(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
} else {
#ifndef USE_ZIGBEE
// the device can be woken up through one of the following signals:
@@ -33,11 +28,12 @@ void DeepSleepComponent::deep_sleep_() {
//
// The system is reset when it wakes up from System OFF mode.
sys_poweroff();
#else
esphome::internal::wakeable_delay(UINT32_MAX);
#endif
}
// It might wake up immediately if k_sem_give was called again after wake up
int ret = k_sem_take(&this->wakeup_sem_, sleep_duration);
if (ret == 0) {
const bool woke = esphome::wake_request_take();
if (woke) {
ESP_LOGD(TAG, "Woken up by another thread");
} else {
ESP_LOGD(TAG, "Timeout expired (normal sleep)");
@@ -29,7 +29,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
protected:
void control(const AlarmControlPanelCall &call) override {
auto state = call.get_state().value_or(ACP_STATE_DISARMED);
auto code = call.get_code();
const auto &code = call.get_code();
switch (state) {
case ACP_STATE_ARMED_AWAY:
if (this->get_requires_code_to_arm()) {
@@ -462,7 +462,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
this->request_headers_.push_back({key, value});
}
void add_collect_header(const char *value) { this->lower_case_collect_headers_.push_back(value); }
void add_collect_header(const char *value) { this->lower_case_collect_headers_.emplace_back(value); }
void init_json(size_t count) { this->json_.init(count); }
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.push_back({key, value}); }
+5 -1
View File
@@ -1,3 +1,5 @@
from operator import itemgetter
from esphome import config_validation as cv
import esphome.codegen as cg
from esphome.const import (
@@ -11,6 +13,7 @@ from esphome.core import ID
from esphome.cpp_generator import MockObj
from .defines import CONF_GRADIENTS, CONF_OPA, LV_DITHER, add_define, add_warning
from .helpers import add_lv_use
from .lv_validation import lv_color, lv_percentage, opacity
from .lvcode import lv
from .types import lv_color_t, lv_gradient_t, lv_opa_t
@@ -50,6 +53,7 @@ GRADIENT_SCHEMA = cv.ensure_list(
async def gradients_to_code(config):
add_lv_use("gradient")
max_stops = 2
if any(CONF_DITHER in x for x in config.get(CONF_GRADIENTS, ())):
add_warning(
@@ -58,7 +62,7 @@ async def gradients_to_code(config):
for gradient in config.get(CONF_GRADIENTS, ()):
var = MockObj(cg.new_Pvariable(gradient[CONF_ID]), "->")
idbase = gradient[CONF_ID].id
stops = gradient[CONF_STOPS]
stops = sorted(gradient[CONF_STOPS], key=itemgetter(CONF_POSITION))
max_stops = max(max_stops, len(stops))
if gradient[CONF_DIRECTION].startswith("VER"):
lv.grad_vertical_init(var)
+26
View File
@@ -864,6 +864,32 @@ void lv_scale_draw_event_cb(lv_event_t *e, int16_t range_start, int16_t range_en
}
#endif // USE_LVGL_SCALE
#ifdef USE_LVGL_GRADIENT
/**
*
* @param dsc The gradient descriptor containing the color stops
* @param pos The current position to calculate the color for
* @return The color for the given position
*/
lv_color_t lv_grad_calculate_color(const lv_grad_dsc_t *dsc, int32_t pos) {
if (dsc->stops_count == 0)
return lv_color_black();
if (dsc->stops_count == 1 || pos <= dsc->stops[0].frac)
return dsc->stops[0].color;
if (pos >= dsc->stops[dsc->stops_count - 1].frac)
return dsc->stops[dsc->stops_count - 1].color;
int i = 1;
while (i < dsc->stops_count && dsc->stops[i].frac < pos)
i++;
auto *stop1 = &dsc->stops[i - 1];
auto *stop2 = &dsc->stops[i];
int32_t range = stop2->frac - stop1->frac;
int32_t offset = pos - stop1->frac;
return lv_color_mix(stop2->color, stop1->color, range == 0 ? 0 : (offset * 255) / range);
}
#endif
static void lv_container_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj) {
LV_TRACE_OBJ_CREATE("begin");
LV_UNUSED(class_p);
+10
View File
@@ -115,6 +115,16 @@ inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images
int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int value);
#endif
#ifdef USE_LVGL_GRADIENT
/**
*
* @param dsc The gradient descriptor containing the color stops
* @param pos The current position to calculate the color for
* @return The color for the given position
*/
lv_color_t lv_grad_calculate_color(const lv_grad_dsc_t *dsc, int32_t pos);
#endif
// Parent class for things that wrap an LVGL object
class LvCompound {
public:
+81 -32
View File
@@ -1,18 +1,27 @@
from collections.abc import Callable
import difflib
import esphome.codegen as cg
from esphome.components.const import KEY_METADATA
import esphome.config_validation as cv
from esphome.const import CONF_FROM, CONF_ID, CONF_TO
from esphome.core import CORE
from esphome.cpp_generator import MockObj, VariableDeclarationExpression, add_global
from esphome.core import CORE, ID
from esphome.cpp_generator import (
MockObj,
MockObjClass,
VariableDeclarationExpression,
add_global,
)
from esphome.loader import get_component
CODEOWNERS = ["@clydebarrow"]
MULTI_CONF = True
DOMAIN = "mapping"
mapping_ns = cg.esphome_ns.namespace("mapping")
mapping_class = mapping_ns.class_("Mapping")
CONF_DEFAULT_VALUE = "default_value"
CONF_ENTRIES = "entries"
CONF_CLASS = "class"
@@ -22,11 +31,18 @@ class IndexType:
Represents a type of index in a map.
"""
def __init__(self, validator, data_type, conversion):
def __init__(
self, validator: Callable, data_type: MockObj, conversion: Callable = None
) -> None:
self.validator = validator
self.data_type = data_type
self.conversion = conversion
async def convert_value(self, value):
if self.conversion:
return self.conversion(value)
return await cg.get_variable(value)
INDEX_TYPES = {
"int": IndexType(cv.int_, cg.int_, int),
@@ -38,6 +54,12 @@ INDEX_TYPES = {
}
class MappingMetaData:
def __init__(self, from_: IndexType, to_: IndexType) -> None:
self.from_ = from_
self.to_ = to_
def to_schema(value):
"""
Generate a schema for the 'to' field of a map. This can be either one of the index types or a class name.
@@ -60,7 +82,7 @@ BASE_SCHEMA = cv.Schema(
)
def get_object_type(to_):
def get_object_type(to_) -> MockObjClass | None:
"""
Get the object type from a string. Possible formats:
xxx The name of a component which defines INSTANCE_TYPE
@@ -81,25 +103,60 @@ def get_object_type(to_):
return None
def get_all_mapping_metadata() -> dict[str, MappingMetaData]:
"""Get all mapping metadata."""
return CORE.data.setdefault(DOMAIN, {}).setdefault(KEY_METADATA, {})
def get_mapping_metadata(mapping_id: str) -> MappingMetaData:
"""Get mapping metadata by ID for use by other components."""
return get_all_mapping_metadata()[mapping_id]
def add_metadata(
mapping_id: ID,
from_: IndexType,
to_: IndexType,
) -> None:
get_all_mapping_metadata()[mapping_id.id] = MappingMetaData(from_, to_)
def map_schema(config):
config = BASE_SCHEMA(config)
if CONF_ENTRIES not in config or not isinstance(config[CONF_ENTRIES], dict):
raise cv.Invalid("an entries list is required for a map")
raise cv.Invalid("an entries dictionary is required for a mapping")
entries = config[CONF_ENTRIES]
if len(entries) == 0:
raise cv.Invalid("Map must have at least one entry")
raise cv.Invalid("A mapping must have at least one entry")
to_ = config[CONF_TO]
if to_ in INDEX_TYPES:
value_type = INDEX_TYPES[to_].validator
value_type = INDEX_TYPES[to_]
else:
value_type = get_object_type(to_)
if value_type is None:
object_type = get_object_type(to_)
if object_type is None:
matches = difflib.get_close_matches(to_, CORE.id_classes)
raise cv.Invalid(
f"No known mappable class name matches '{to_}'; did you mean one of {', '.join(matches)}?"
)
value_type = cv.use_id(value_type)
config[CONF_ENTRIES] = {k: value_type(v) for k, v in entries.items()}
validator = cv.use_id(object_type)
value_type = IndexType(validator, object_type)
config[CONF_ENTRIES] = {k: value_type.validator(v) for k, v in entries.items()}
if (default_value := config.get(CONF_DEFAULT_VALUE)) is not None:
config[CONF_DEFAULT_VALUE] = value_type.validator(default_value)
unexpected_keys = config.keys() - {
CONF_ENTRIES,
CONF_TO,
CONF_FROM,
CONF_ID,
CONF_DEFAULT_VALUE,
}
if unexpected_keys:
errors = [
cv.Invalid(f"Unexpected key '{k}'", path=[k]) for k in unexpected_keys
]
raise cv.MultipleInvalid(errors)
add_metadata(config[CONF_ID], INDEX_TYPES[config[CONF_FROM]], value_type)
return config
@@ -107,29 +164,19 @@ CONFIG_SCHEMA = map_schema
async def to_code(config):
entries = config[CONF_ENTRIES]
from_ = config[CONF_FROM]
to_ = config[CONF_TO]
index_conversion = INDEX_TYPES[from_].conversion
index_type = INDEX_TYPES[from_].data_type
if to_ in INDEX_TYPES:
value_conversion = INDEX_TYPES[to_].conversion
value_type = INDEX_TYPES[to_].data_type
entries = {
index_conversion(key): value_conversion(value)
for key, value in entries.items()
}
else:
entries = {
index_conversion(key): await cg.get_variable(value)
for key, value in entries.items()
}
value_type = get_object_type(to_)
if list(entries.values())[0].op != ".":
value_type = value_type.operator("ptr")
varid = config[CONF_ID]
metadata = get_mapping_metadata(varid.id)
entries = {
metadata.from_.conversion(key): await metadata.to_.convert_value(value)
for key, value in config[CONF_ENTRIES].items()
}
value_type = metadata.to_.data_type
# entries guaranteed to be non-empty here.
value_0 = list(entries.values())[0]
if isinstance(value_0, MockObj) and value_0.op != ".":
value_type = value_type.operator("ptr")
varid.type = mapping_class.template(
index_type,
metadata.from_.data_type,
value_type,
)
var = MockObj(varid, ".")
@@ -139,4 +186,6 @@ async def to_code(config):
for key, value in entries.items():
cg.add(var.set(key, value))
if (default_value := config.get(CONF_DEFAULT_VALUE)) is not None:
cg.add(var.set_default_value(await metadata.to_.convert_value(default_value)))
return var
+9
View File
@@ -40,6 +40,9 @@ template<typename K, typename V> class Mapping {
if (it != this->map_.end()) {
return V{it->second};
}
if (this->default_value_.has_value()) {
return this->default_value_.value();
}
if constexpr (std::is_pointer_v<K>) {
esph_log_e(TAG, "Key '%p' not found in mapping", key);
} else if constexpr (std::is_same_v<K, std::string>) {
@@ -69,11 +72,17 @@ template<typename K, typename V> class Mapping {
if (it != this->map_.end()) {
return it->second.c_str(); // safe since value remains in map
}
if (this->default_value_.has_value()) {
return this->default_value_.value();
}
return "";
}
void set_default_value(const V &default_value) { this->default_value_ = default_value; }
protected:
std::map<key_t, value_t, std::less<key_t>, RAMAllocator<std::pair<key_t, value_t>>> map_;
std::optional<V> default_value_{};
};
} // namespace esphome::mapping
+20 -8
View File
@@ -141,6 +141,22 @@ CONF_UICR_ERASE = "uicr_erase"
VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3]
_DFU_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DeviceFirmwareUpdate),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
)
def _dfu_schema(value: bool | ConfigType) -> ConfigType:
if isinstance(value, bool):
if not value:
raise cv.Invalid("Use 'dfu: true' or specify a configuration dict")
return _DFU_SCHEMA({})
return _DFU_SCHEMA(value)
CONFIG_SCHEMA = cv.All(
_detect_bootloader,
set_core_data,
@@ -150,12 +166,7 @@ CONFIG_SCHEMA = cv.All(
cv.string_strict, cv.ByteLength(max=BOARD_MAX_LENGTH)
),
cv.Optional(KEY_BOOTLOADER): cv.one_of(*BOOTLOADERS, lower=True),
cv.Optional(CONF_DFU): cv.Schema(
{
cv.GenerateID(): cv.declare_id(DeviceFirmwareUpdate),
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
),
cv.Optional(CONF_DFU): _dfu_schema,
cv.Optional(CONF_DCDC, default=True): cv.boolean,
cv.Optional(CONF_REG0): cv.Schema(
{
@@ -321,8 +332,9 @@ async def to_code(config: ConfigType) -> None:
async def _dfu_to_code(dfu_config):
cg.add_define("USE_NRF52_DFU")
var = cg.new_Pvariable(dfu_config[CONF_ID])
pin = await cg.gpio_pin_expression(dfu_config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
if CONF_RESET_PIN in dfu_config:
pin = await cg.gpio_pin_expression(dfu_config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
zephyr_add_prj_conf("CDC_ACM_DTE_RATE_CALLBACK_SUPPORT", True)
await cg.register_component(var, dfu_config)
+20 -7
View File
@@ -2,24 +2,34 @@
#ifdef USE_NRF52_DFU
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/components/zephyr/cdc_acm.h"
namespace esphome {
namespace nrf52 {
#include <hal/nrf_power.h>
namespace esphome::nrf52 {
static const char *const TAG = "dfu";
static const uint32_t DFU_DBL_RESET_MAGIC = 0x5A1AD5; // SALADS
static const uint8_t DFU_MAGIC_UF2_RESET = 0x57; // Adafruit nRF52 bootloader UF2 magic
void DeviceFirmwareUpdate::setup() {
this->reset_pin_->setup();
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
}
#if defined(CONFIG_CDC_ACM_DTE_RATE_CALLBACK_SUPPORT)
zephyr::global_cdc_acm->add_on_rate_callback([this](const device *, uint32_t rate) {
if (rate == 1200) {
volatile uint32_t *dbl_reset_mem = (volatile uint32_t *) 0x20007F7C;
(*dbl_reset_mem) = DFU_DBL_RESET_MAGIC;
this->reset_pin_->digital_write(true);
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(true);
} else {
NRF_POWER->GPREGRET = DFU_MAGIC_UF2_RESET;
App.reboot();
}
}
});
#endif
@@ -27,10 +37,13 @@ void DeviceFirmwareUpdate::setup() {
void DeviceFirmwareUpdate::dump_config() {
ESP_LOGCONFIG(TAG, "DFU:");
LOG_PIN(" RESET Pin: ", this->reset_pin_);
if (this->reset_pin_ != nullptr) {
LOG_PIN(" RESET Pin: ", this->reset_pin_);
} else {
ESP_LOGCONFIG(TAG, " Method: GPREGRET");
}
}
} // namespace nrf52
} // namespace esphome
} // namespace esphome::nrf52
#endif
+3 -5
View File
@@ -5,8 +5,7 @@
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
namespace esphome {
namespace nrf52 {
namespace esphome::nrf52 {
class DeviceFirmwareUpdate : public Component {
public:
void setup() override;
@@ -14,10 +13,9 @@ class DeviceFirmwareUpdate : public Component {
void dump_config() override;
protected:
GPIOPin *reset_pin_;
GPIOPin *reset_pin_{nullptr};
};
} // namespace nrf52
} // namespace esphome
} // namespace esphome::nrf52
#endif
+7 -3
View File
@@ -433,13 +433,17 @@ void Pipsolar::handle_qpigs_(const char *message) {
}
void Pipsolar::handle_qmod_(const char *message) {
std::string mode;
char device_mode = char(message[1]);
if (this->last_qmod_) {
this->last_qmod_->publish_state(message);
}
// QMOD response is "(M" where M is the device-mode character. Bail out if the
// message is shorter than 2 chars (e.g. empty error response from
// handle_poll_error_) — reading message[1] would otherwise be out of bounds.
if (message[0] == '\0' || message[1] == '\0')
return;
if (this->device_mode_) {
mode = device_mode;
std::string mode;
mode = char(message[1]);
this->device_mode_->publish_state(mode);
}
}
+1 -1
View File
@@ -451,7 +451,7 @@ class SPIDevice : public SPIClient {
uint8_t read_byte() { return this->delegate_->transfer(0); }
void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
void read_array(uint8_t *data, size_t length) { this->delegate_->read_array(data, length); }
/**
* Write a single data item, up to 32 bits.
-10
View File
@@ -68,13 +68,8 @@ void TLC5971::transfer_(uint8_t send) {
uint8_t startbit = 0x80;
bool towrite, lastmosi = !(send & startbit);
uint8_t bitdelay_us = (1000000 / 1000000) / 2;
for (uint8_t b = startbit; b != 0; b = b >> 1) {
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
towrite = send & b;
if ((lastmosi != towrite)) {
this->data_pin_->digital_write(towrite);
@@ -82,11 +77,6 @@ void TLC5971::transfer_(uint8_t send) {
}
this->clock_pin_->digital_write(true);
if (bitdelay_us) {
delayMicroseconds(bitdelay_us);
}
this->clock_pin_->digital_write(false);
}
}
+1 -2
View File
@@ -6,7 +6,6 @@
#include <cinttypes>
#include <cstdint>
#ifdef USE_ESP32
#include <soc/soc_caps.h>
#include "esp_idf_version.h"
#include "esp_task_wdt.h"
#endif
@@ -40,7 +39,7 @@ void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
#ifdef USE_ESP32
esp_task_wdt_config_t wdt_config = {
.timeout_ms = timeout_ms,
.idle_core_mask = (1 << SOC_CPU_CORES_NUM) - 1,
.idle_core_mask = (1U << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1U,
.trigger_panic = true,
};
esp_task_wdt_reconfigure(&wdt_config);
+2 -8
View File
@@ -4,9 +4,7 @@
#include <zephyr/settings/settings.h>
#include <zephyr/storage/flash_map.h>
#include "esphome/core/hal.h"
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
#endif
#include "esphome/core/wake.h"
extern "C" {
#include <zboss_api.h>
@@ -119,11 +117,7 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) {
/* Set default response value. */
p_device_cb_param->status = RET_OK;
#ifdef USE_DEEP_SLEEP
if (auto *ds = deep_sleep::global_deep_sleep.load()) {
ds->wakeup();
}
#endif
esphome::wake_loop_threadsafe();
// endpoints are enumerated from 1
if (global_zigbee->callbacks_.size() >= endpoint) {
+3
View File
@@ -370,6 +370,9 @@ class Application {
#elif defined(USE_ESP8266)
/// Wake from ISR (ESP8266). No task_woken arg — no FreeRTOS. Caller must be IRAM_ATTR.
static void IRAM_ATTR ESPHOME_ALWAYS_INLINE wake_loop_isrsafe() { esphome::wake_loop_isrsafe(); }
#elif defined(USE_ZEPHYR)
/// Wake from ISR (Zephyr). No task_woken arg — k_sem_give() handles ISR scheduling internally.
static void wake_loop_isrsafe() { esphome::wake_loop_isrsafe(); }
#endif
/// Wake from any context (ISR, thread, callback).
+1 -1
View File
@@ -812,7 +812,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
"wake/wake_host.cpp": {
PlatformFramework.HOST_NATIVE,
},
"wake/wake_generic.cpp": {
"wake/wake_zephyr.cpp": {
PlatformFramework.NRF52_ZEPHYR,
},
# Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered
+1
View File
@@ -96,6 +96,7 @@
#define USE_LVGL_CHECKBOX
#define USE_LVGL_DROPDOWN
#define USE_LVGL_FONT
#define USE_LVGL_GRADIENT
#define USE_LVGL_IMAGE
#define USE_LVGL_IMAGEBUTTON
#define USE_LVGL_KEY_LISTENER
+3 -1
View File
@@ -69,6 +69,8 @@ __attribute__((always_inline)) inline bool wake_request_take() {
#include "esphome/core/wake/wake_rp2040.h"
#elif defined(USE_HOST)
#include "esphome/core/wake/wake_host.h"
#elif defined(USE_ZEPHYR)
#include "esphome/core/wake/wake_zephyr.h"
#else
#include "esphome/core/wake/wake_generic.h"
#error "wake.h: wake_loop_threadsafe() is not implemented for this platform"
#endif
+4
View File
@@ -36,6 +36,10 @@ inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
// Yield even on the already-woken fast path so callers in tight loops
// (e.g. lwIP raw TCP wait_for_data_) make forward progress when ISRs
// keep re-setting g_main_loop_woke between iterations.
delay(0);
return;
}
esp_delay(ms, []() { return !g_main_loop_woke; });
-17
View File
@@ -1,17 +0,0 @@
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_LIBRETINY) && !defined(USE_ESP8266) && !defined(USE_RP2040) && \
!defined(USE_HOST)
#include "esphome/core/wake.h"
namespace esphome {
// === Wake-requested flag storage ===
// Fallback platforms (currently only Zephyr/NRF52) are ESPHOME_THREAD_SINGLE.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
} // namespace esphome
#endif // fallback guard
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include "esphome/core/defines.h"
#if !defined(USE_ESP32) && !defined(USE_LIBRETINY) && !defined(USE_ESP8266) && !defined(USE_RP2040) && \
!defined(USE_HOST)
#include "esphome/core/hal.h"
namespace esphome {
/// Zephyr is currently the only platform without a wake mechanism.
/// wake_loop_threadsafe() is a no-op and wakeable_delay() falls back to delay().
/// TODO: implement proper Zephyr wake using k_poll / k_sem or similar.
inline void wake_loop_threadsafe() {}
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
namespace internal {
inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
delay(ms);
}
} // namespace internal
} // namespace esphome
#endif // fallback guard
+4
View File
@@ -36,6 +36,10 @@ void wakeable_delay(uint32_t ms) {
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
// Yield even on the already-woken fast path so callers in tight loops
// (e.g. lwIP raw TCP wait_for_data_) make forward progress when async
// wakes keep re-setting g_main_loop_woke between iterations.
yield();
return;
}
s_delay_expired = false;
+41
View File
@@ -0,0 +1,41 @@
#include "esphome/core/defines.h"
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
#include "esphome/core/wake.h"
#include <zephyr/kernel.h>
namespace esphome {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
K_SEM_DEFINE(esphome_wake_sem, 0, 1);
// === Wake-requested flag storage ===
// Zephyr has preemptive threads and ISRs, so wake_loop_threadsafe() is genuinely
// called cross-context. volatile uint8_t is sufficient because: (1) Cortex-M
// 8-bit aligned store/load is a single non-tearing instruction, and (2) every
// producer pairs the store with k_sem_give() (release barrier) and the consumer
// pairs the load with k_sem_take() (acquire barrier).
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
volatile uint8_t g_wake_requested = 0;
void wake_loop_threadsafe() {
wake_request_set();
k_sem_give(&esphome_wake_sem);
}
namespace internal {
void wakeable_delay(uint32_t ms) {
if (ms == 0) [[unlikely]] {
yield();
return;
}
k_sem_take(&esphome_wake_sem, ms == UINT32_MAX ? K_FOREVER : K_MSEC(ms));
}
} // namespace internal
} // namespace esphome
#endif // USE_ZEPHYR
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
namespace esphome {
/// Zephyr: wakes the main loop via k_sem_give(). Thread- and ISR-safe.
/// Defined in wake_zephyr.cpp.
void wake_loop_threadsafe();
inline void wake_loop_any_context() { wake_loop_threadsafe(); }
/// ISR-safe: no task_woken arg because Zephyr's k_sem_give() does its own ISR
/// scheduling. Forwards to wake_loop_threadsafe().
inline void wake_loop_isrsafe() { wake_loop_threadsafe(); }
namespace internal {
/// Zephyr wakeable_delay uses k_sem_take() with a timeout — defined in wake_zephyr.cpp.
void wakeable_delay(uint32_t ms);
} // namespace internal
} // namespace esphome
#endif // USE_ZEPHYR
+8 -2
View File
@@ -9,16 +9,22 @@ import logging
from pathlib import Path
import sys
from types import ModuleType
from typing import Any
from typing import TYPE_CHECKING, Any
from esphome.const import SOURCE_FILE_EXTENSIONS
from esphome.core import CORE
from esphome.types import ConfigType
if TYPE_CHECKING:
from esphome.cpp_generator import MockObjClass
# `esphome.core.config` is imported lazily in `_lookup_module` when the
# "esphome" pseudo-component is first resolved. It pulls in
# `esphome.automation` and `esphome.config_validation`, which together
# dominate `esphome.__main__` startup cost when loaded eagerly.
# `esphome.cpp_generator` is similarly avoided at module scope; it pulls
# in `esphome.yaml_util` and is only needed for the `MockObjClass` type
# annotation, which is resolved lazily via `TYPE_CHECKING`.
_LOGGER = logging.getLogger(__name__)
@@ -97,7 +103,7 @@ class ComponentManifest:
return getattr(self.module, "CODEOWNERS", [])
@property
def instance_type(self) -> list[str]:
def instance_type(self) -> "MockObjClass | None":
return getattr(self.module, "INSTANCE_TYPE", None)
@property
+2
View File
@@ -21,6 +21,7 @@ mapping:
entries:
clear-night: image_1
sunny: image_2
default_value: image_1
- id: weather_map_2
from: string
to: image
@@ -35,6 +36,7 @@ mapping:
2: "two"
3: "three"
77: "seventy-seven"
default_value: unknown
- id: string_map
from: string
to: int
+1 -1
View File
@@ -4,7 +4,7 @@ packages:
display:
spi_id: spi_bus
platform: ili9xxx
platform: mipi_spi
id: main_lcd
model: ili9342
cs_pin: 12
@@ -4,7 +4,7 @@ packages:
display:
spi_id: spi_bus
platform: ili9xxx
platform: mipi_spi
id: main_lcd
model: ili9342
cs_pin: 5
@@ -4,7 +4,7 @@ packages:
display:
spi_id: spi_bus
platform: ili9xxx
platform: mipi_spi
id: main_lcd
model: ili9342
data_rate: 31.25MHz
@@ -0,0 +1,9 @@
nrf52:
dfu:
reset_pin:
number: 14
inverted: true
mode:
output: true
reg0:
voltage: 1.8V
@@ -1,9 +1,4 @@
nrf52:
dfu:
reset_pin:
number: 14
inverted: true
mode:
output: true
dfu: true
reg0:
voltage: 1.8V