mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
Merge branch 'dev' into ota-watchdogmanager
This commit is contained in:
+25
-4
@@ -21,7 +21,7 @@ import argcomplete
|
||||
# Note: Do not import modules from esphome.components here, as this would
|
||||
# cause them to be loaded before external components are processed, resulting
|
||||
# in the built-in version being used instead of the external component one.
|
||||
from esphome import const, writer, yaml_util
|
||||
from esphome import const
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
||||
from esphome.const import (
|
||||
@@ -72,7 +72,12 @@ from esphome.util import (
|
||||
run_external_process,
|
||||
safe_print,
|
||||
)
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
# Keep expensive imports (zeroconf, writer, yaml_util, etc.) out of this
|
||||
# module's top level. Every `esphome` invocation — including fast paths
|
||||
# like `esphome version` — pays the cost of what's imported here before
|
||||
# any command runs. Import inside the function that needs it instead.
|
||||
# `script/check_import_time.py` enforces a budget in CI.
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -241,6 +246,8 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
"""
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
|
||||
return None
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
_LOGGER.info("Discovering devices...")
|
||||
if not (discovered := discover_mdns_devices(CORE.name)):
|
||||
_LOGGER.warning(
|
||||
@@ -660,7 +667,7 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
def _wrap_to_code(name, comp, yaml_util):
|
||||
coro = coroutine(comp.to_code)
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
@@ -680,6 +687,8 @@ def wrap_to_code(name, comp):
|
||||
|
||||
|
||||
def write_cpp(config: ConfigType, native_idf: bool = False) -> int:
|
||||
from esphome import writer
|
||||
|
||||
if not get_bool_env(ENV_NOGITIGNORE):
|
||||
writer.write_gitignore()
|
||||
|
||||
@@ -691,17 +700,21 @@ def write_cpp(config: ConfigType, native_idf: bool = False) -> int:
|
||||
|
||||
|
||||
def generate_cpp_contents(config: ConfigType) -> None:
|
||||
from esphome import yaml_util
|
||||
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_component_configs(CORE.config):
|
||||
if component.to_code is not None:
|
||||
coro = wrap_to_code(name, component)
|
||||
coro = _wrap_to_code(name, component, yaml_util)
|
||||
CORE.add_job(coro, conf)
|
||||
|
||||
CORE.flush_tasks()
|
||||
|
||||
|
||||
def write_cpp_file(native_idf: bool = False) -> int:
|
||||
from esphome import writer
|
||||
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
writer.write_cpp(code_s)
|
||||
|
||||
@@ -1180,6 +1193,8 @@ def command_wizard(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import yaml_util
|
||||
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
output = yaml_util.dump(config, args.show_secrets)
|
||||
@@ -1321,6 +1336,8 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
|
||||
def command_clean_all(args: ArgsProtocol) -> int | None:
|
||||
from esphome import writer
|
||||
|
||||
try:
|
||||
writer.clean_all(args.configuration)
|
||||
except OSError as err:
|
||||
@@ -1336,6 +1353,8 @@ def command_version(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import writer
|
||||
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
@@ -1538,6 +1557,8 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
|
||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import yaml_util
|
||||
|
||||
new_name = args.name
|
||||
for c in new_name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
// Implementation based on:
|
||||
// https://github.com/sciosense/ENS160_driver
|
||||
|
||||
// For best performance, the sensor shall be operated in normal indoor air in the range -5 to 60°C
|
||||
// (typical: 25°C); relative humidity: 20 to 80%RH (typical: 50%RH), non-condensing with no aggressive
|
||||
// or poisonous gases present. Prolonged exposure to environments outside these conditions can affect
|
||||
// performance and lifetime of the sensor.
|
||||
// The sensor is designed for indoor use and is not waterproof or dustproof. It should be protected from
|
||||
// water, condensation, dust, and aggressive gases. Note that the status will only be stored in non-volatile
|
||||
// memory after an initial 24 h of continuous operation. If unpowered before the conclusion of that period,
|
||||
// the ENS160 will resume "Initial Start-up" mode after re-powering.
|
||||
|
||||
#include "ens160_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -14,7 +23,9 @@ namespace ens160_base {
|
||||
|
||||
static const char *const TAG = "ens160";
|
||||
|
||||
static const uint8_t ENS160_BOOTING = 10;
|
||||
// Datasheet specifies 10ms, but some users report that 10ms is not sufficient for the
|
||||
// sensor to boot and be ready for commands. 11ms seems to be a safe value.
|
||||
static const uint8_t ENS160_BOOTING = 11;
|
||||
|
||||
static const uint16_t ENS160_PART_ID = 0x0160;
|
||||
|
||||
@@ -91,6 +102,8 @@ void ENS160Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(ENS160_BOOTING);
|
||||
|
||||
// clear command
|
||||
if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_NOP)) {
|
||||
this->error_code_ = WRITE_FAILED;
|
||||
@@ -102,6 +115,7 @@ void ENS160Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(ENS160_BOOTING);
|
||||
|
||||
// read firmware version
|
||||
if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER)) {
|
||||
@@ -109,6 +123,8 @@ void ENS160Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(ENS160_BOOTING);
|
||||
|
||||
uint8_t version_data[3];
|
||||
if (!this->read_bytes(ENS160_REG_GPR_READ_4, version_data, 3)) {
|
||||
this->error_code_ = READ_FAILED;
|
||||
@@ -223,7 +239,6 @@ void ENS160Component::update() {
|
||||
if (this->aqi_ != nullptr) {
|
||||
// remove reserved bits, just in case they are used in future
|
||||
data_aqi = ENS160_DATA_AQI & data_aqi;
|
||||
|
||||
this->aqi_->publish_state(data_aqi);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ extern "C" __attribute__((weak)) void initArduino() {}
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { vPortYield(); }
|
||||
// yield(), delay(), micros(), millis_64() inlined in hal.h.
|
||||
// Use xTaskGetTickCount() when tick rate is 1 kHz (ESPHome's default via sdkconfig),
|
||||
// falling back to esp_timer for non-standard rates. IRAM_ATTR is required because
|
||||
// Wiegand and ZyAura call millis() from IRAM_ATTR ISR handlers on ESP32.
|
||||
@@ -37,15 +37,6 @@ uint32_t IRAM_ATTR HOT millis() {
|
||||
return micros_to_millis(static_cast<uint64_t>(esp_timer_get_time()));
|
||||
#endif
|
||||
}
|
||||
// millis_64() stays on esp_timer — a different clock from xTaskGetTickCount(). This is
|
||||
// safe because the two are never cross-compared: millis() values are only used for
|
||||
// millis()-vs-millis() deltas (feed_wdt, warn_blocking, component start time), while
|
||||
// millis_64() is used by the Scheduler and uptime sensors. On ESP32 (USE_NATIVE_64BIT_TIME),
|
||||
// Scheduler::millis_64_from_(now) discards the 32-bit now and calls millis_64() directly,
|
||||
// so the Scheduler is internally consistent on the esp_timer clock.
|
||||
uint64_t HOT millis_64() { return micros_to_millis<uint64_t>(static_cast<uint64_t>(esp_timer_get_time())); }
|
||||
void HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
esp_restart();
|
||||
|
||||
@@ -15,7 +15,7 @@ extern "C" {
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
// yield(), micros(), millis_64() inlined in hal.h.
|
||||
// Fast accumulator replacement for Arduino's millis() (~3.3 μs via 4× 64-bit
|
||||
// multiplies on the LX106). Tracks a running ms counter from 32-bit
|
||||
// system_get_time() deltas using pure 32-bit ops. Installed as __wrap_millis
|
||||
@@ -66,7 +66,6 @@ uint32_t IRAM_ATTR HOT millis() {
|
||||
xt_wsr_ps(ps);
|
||||
return result;
|
||||
}
|
||||
uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
// Poll-based delay that avoids ::delay() — Arduino's __delay has an intra-object
|
||||
// call to the original millis() that --wrap can't intercept, so calling ::delay()
|
||||
// would keep the slow Arduino millis body alive in IRAM. optimistic_yield still
|
||||
@@ -85,8 +84,7 @@ void HOT delay(uint32_t ms) {
|
||||
optimistic_yield(1000);
|
||||
}
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
// delayMicroseconds(), arch_feed_wdt(), and progmem_read_*() are inlined in hal/hal_esp8266.h.
|
||||
void arch_restart() {
|
||||
system_restart();
|
||||
// restart() doesn't always end execution
|
||||
@@ -95,17 +93,6 @@ void arch_restart() {
|
||||
}
|
||||
}
|
||||
void arch_init() {}
|
||||
void HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||
return pgm_read_byte(addr); // NOLINT
|
||||
}
|
||||
const char *progmem_read_ptr(const char *const *addr) {
|
||||
return reinterpret_cast<const char *>(pgm_read_ptr(addr)); // NOLINT
|
||||
}
|
||||
uint16_t progmem_read_uint16(const uint16_t *addr) {
|
||||
return pgm_read_word(addr); // NOLINT
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ espnow_ns = cg.esphome_ns.namespace("espnow")
|
||||
ESPNowComponent = espnow_ns.class_("ESPNowComponent", cg.Component)
|
||||
|
||||
# Handler interfaces that other components can use to register callbacks
|
||||
ESPNowReceivedPacketHandler = espnow_ns.class_("ESPNowReceivedPacketHandler")
|
||||
ESPNowReceivePacketHandler = espnow_ns.class_("ESPNowReceivePacketHandler")
|
||||
ESPNowUnknownPeerHandler = espnow_ns.class_("ESPNowUnknownPeerHandler")
|
||||
ESPNowBroadcastedHandler = espnow_ns.class_("ESPNowBroadcastedHandler")
|
||||
ESPNowBroadcastHandler = espnow_ns.class_("ESPNowBroadcastHandler")
|
||||
|
||||
ESPNowRecvInfo = espnow_ns.class_("ESPNowRecvInfo")
|
||||
ESPNowRecvInfoConstRef = ESPNowRecvInfo.operator("const").operator("ref")
|
||||
@@ -48,10 +48,10 @@ OnUnknownPeerTrigger = espnow_ns.class_(
|
||||
"OnUnknownPeerTrigger", ESPNowHandlerTrigger, ESPNowUnknownPeerHandler
|
||||
)
|
||||
OnReceiveTrigger = espnow_ns.class_(
|
||||
"OnReceiveTrigger", ESPNowHandlerTrigger, ESPNowReceivedPacketHandler
|
||||
"OnReceiveTrigger", ESPNowHandlerTrigger, ESPNowReceivePacketHandler
|
||||
)
|
||||
OnBroadcastedTrigger = espnow_ns.class_(
|
||||
"OnBroadcastedTrigger", ESPNowHandlerTrigger, ESPNowBroadcastedHandler
|
||||
OnBroadcastTrigger = espnow_ns.class_(
|
||||
"OnBroadcastTrigger", ESPNowHandlerTrigger, ESPNowBroadcastHandler
|
||||
)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_ON_BROADCAST): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnBroadcastedTrigger),
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnBroadcastTrigger),
|
||||
cv.Optional(CONF_ADDRESS): cv.mac_address,
|
||||
}
|
||||
),
|
||||
@@ -140,11 +140,11 @@ async def to_code(config):
|
||||
|
||||
for on_receive in config.get(CONF_ON_RECEIVE, []):
|
||||
trigger = await _trigger_to_code(on_receive)
|
||||
cg.add(var.register_received_handler(trigger))
|
||||
cg.add(var.register_receive_handler(trigger))
|
||||
|
||||
for on_receive in config.get(CONF_ON_BROADCAST, []):
|
||||
trigger = await _trigger_to_code(on_receive)
|
||||
cg.add(var.register_broadcasted_handler(trigger))
|
||||
cg.add(var.register_broadcast_handler(trigger))
|
||||
|
||||
|
||||
# ========================================== A C T I O N S ================================================
|
||||
|
||||
@@ -67,6 +67,7 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||
}
|
||||
|
||||
@@ -75,7 +76,6 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
||||
this->error_.stop();
|
||||
}
|
||||
|
||||
protected:
|
||||
ActionList<Ts...> sent_;
|
||||
ActionList<Ts...> error_;
|
||||
|
||||
@@ -89,7 +89,7 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
||||
template<typename... Ts> class AddPeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
TEMPLATABLE_VALUE(peer_address_t, address);
|
||||
|
||||
public:
|
||||
protected:
|
||||
void play(const Ts &...x) override {
|
||||
peer_address_t address = this->address_.value(x...);
|
||||
this->parent_->add_peer(address.data());
|
||||
@@ -99,7 +99,7 @@ template<typename... Ts> class AddPeerAction : public Action<Ts...>, public Pare
|
||||
template<typename... Ts> class DeletePeerAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
TEMPLATABLE_VALUE(peer_address_t, address);
|
||||
|
||||
public:
|
||||
protected:
|
||||
void play(const Ts &...x) override {
|
||||
peer_address_t address = this->address_.value(x...);
|
||||
this->parent_->del_peer(address.data());
|
||||
@@ -107,8 +107,9 @@ template<typename... Ts> class DeletePeerAction : public Action<Ts...>, public P
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
|
||||
protected:
|
||||
void play(const Ts &...x) override {
|
||||
if (this->parent_->is_wifi_enabled()) {
|
||||
return;
|
||||
@@ -125,9 +126,9 @@ class OnReceiveTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_t *,
|
||||
memcpy(this->address_, address.data(), ESP_NOW_ETH_ALEN);
|
||||
}
|
||||
|
||||
explicit OnReceiveTrigger() : has_address_(false) {}
|
||||
explicit OnReceiveTrigger() {}
|
||||
|
||||
bool on_received(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
|
||||
if (!match)
|
||||
return false;
|
||||
@@ -138,7 +139,7 @@ class OnReceiveTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_t *,
|
||||
|
||||
protected:
|
||||
bool has_address_{false};
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN];
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN]{};
|
||||
};
|
||||
class OnUnknownPeerTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
public ESPNowUnknownPeerHandler {
|
||||
@@ -148,15 +149,15 @@ class OnUnknownPeerTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_
|
||||
return false; // Return false to continue processing other internal handlers
|
||||
}
|
||||
};
|
||||
class OnBroadcastedTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
public ESPNowBroadcastedHandler {
|
||||
class OnBroadcastTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_t *, uint8_t>,
|
||||
public ESPNowBroadcastHandler {
|
||||
public:
|
||||
explicit OnBroadcastedTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
|
||||
explicit OnBroadcastTrigger(std::array<uint8_t, ESP_NOW_ETH_ALEN> address) : has_address_(true) {
|
||||
memcpy(this->address_, address.data(), ESP_NOW_ETH_ALEN);
|
||||
}
|
||||
explicit OnBroadcastedTrigger() : has_address_(false) {}
|
||||
explicit OnBroadcastTrigger() {}
|
||||
|
||||
bool on_broadcasted(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override {
|
||||
bool match = !this->has_address_ || (memcmp(this->address_, info.src_addr, ESP_NOW_ETH_ALEN) == 0);
|
||||
if (!match)
|
||||
return false;
|
||||
@@ -167,7 +168,7 @@ class OnBroadcastedTrigger : public Trigger<const ESPNowRecvInfo &, const uint8_
|
||||
|
||||
protected:
|
||||
bool has_address_{false};
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN];
|
||||
uint8_t address_[ESP_NOW_ETH_ALEN]{};
|
||||
};
|
||||
|
||||
} // namespace esphome::espnow
|
||||
|
||||
@@ -299,13 +299,13 @@ void ESPNowComponent::loop() {
|
||||
format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
|
||||
#endif
|
||||
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
|
||||
for (auto *handler : this->broadcasted_handlers_) {
|
||||
if (handler->on_broadcasted(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||
for (auto *handler : this->broadcast_handlers_) {
|
||||
if (handler->on_broadcast(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||
break; // If a handler returns true, stop processing further handlers
|
||||
}
|
||||
} else {
|
||||
for (auto *handler : this->received_handlers_) {
|
||||
if (handler->on_received(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||
for (auto *handler : this->receive_handlers_) {
|
||||
if (handler->on_receive(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||
break; // If a handler returns true, stop processing further handlers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ using peer_address_t = std::array<uint8_t, ESP_NOW_ETH_ALEN>;
|
||||
enum class ESPNowTriggers : uint8_t {
|
||||
TRIGGER_NONE = 0,
|
||||
ON_NEW_PEER = 1,
|
||||
ON_RECEIVED = 2,
|
||||
ON_BROADCASTED = 3,
|
||||
ON_RECEIVE = 2,
|
||||
ON_BROADCAST = 3,
|
||||
ON_SUCCEED = 10,
|
||||
ON_FAILED = 11,
|
||||
};
|
||||
@@ -74,18 +74,18 @@ class ESPNowReceivedPacketHandler {
|
||||
/// @param data Pointer to the received data payload
|
||||
/// @param size Size of the received data in bytes
|
||||
/// @return true if the packet was handled, false otherwise
|
||||
virtual bool on_received(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
virtual bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
};
|
||||
/// Handler interface for receiving broadcasted ESPNow packets
|
||||
/// Handler interface for receiving ESPNow broadcast packets
|
||||
/// Components should inherit from this class to handle incoming ESPNow data
|
||||
class ESPNowBroadcastedHandler {
|
||||
class ESPNowBroadcastHandler {
|
||||
public:
|
||||
/// Called when a broadcasted ESPNow packet is received
|
||||
/// Called when an ESPNow broadcast packet is received
|
||||
/// @param info Information about the received packet (sender MAC, etc.)
|
||||
/// @param data Pointer to the received data payload
|
||||
/// @param size Size of the received data in bytes
|
||||
/// @return true if the packet was handled, false otherwise
|
||||
virtual bool on_broadcasted(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
virtual bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) = 0;
|
||||
};
|
||||
|
||||
class ESPNowComponent : public Component {
|
||||
@@ -136,13 +136,11 @@ class ESPNowComponent : public Component {
|
||||
esp_err_t send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
|
||||
const send_callback_t &callback = nullptr);
|
||||
|
||||
void register_received_handler(ESPNowReceivedPacketHandler *handler) { this->received_handlers_.push_back(handler); }
|
||||
void register_receive_handler(ESPNowReceivedPacketHandler *handler) { this->receive_handlers_.push_back(handler); }
|
||||
void register_unknown_peer_handler(ESPNowUnknownPeerHandler *handler) {
|
||||
this->unknown_peer_handlers_.push_back(handler);
|
||||
}
|
||||
void register_broadcasted_handler(ESPNowBroadcastedHandler *handler) {
|
||||
this->broadcasted_handlers_.push_back(handler);
|
||||
}
|
||||
void register_broadcast_handler(ESPNowBroadcastHandler *handler) { this->broadcast_handlers_.push_back(handler); }
|
||||
|
||||
protected:
|
||||
friend void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size);
|
||||
@@ -156,8 +154,8 @@ class ESPNowComponent : public Component {
|
||||
void send_();
|
||||
|
||||
std::vector<ESPNowUnknownPeerHandler *> unknown_peer_handlers_;
|
||||
std::vector<ESPNowReceivedPacketHandler *> received_handlers_;
|
||||
std::vector<ESPNowBroadcastedHandler *> broadcasted_handlers_;
|
||||
std::vector<ESPNowReceivedPacketHandler *> receive_handlers_;
|
||||
std::vector<ESPNowBroadcastHandler *> broadcast_handlers_;
|
||||
|
||||
std::vector<ESPNowPeer> peers_{};
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ void ESPNowTransport::setup() {
|
||||
this->peer_address_[5]);
|
||||
|
||||
// Register received handler
|
||||
this->parent_->register_received_handler(this);
|
||||
this->parent_->register_receive_handler(this);
|
||||
|
||||
// Register broadcasted handler
|
||||
this->parent_->register_broadcasted_handler(this);
|
||||
// Register broadcast handler
|
||||
this->parent_->register_broadcast_handler(this);
|
||||
}
|
||||
|
||||
void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
|
||||
@@ -56,7 +56,7 @@ void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
|
||||
});
|
||||
}
|
||||
|
||||
bool ESPNowTransport::on_received(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
bool ESPNowTransport::on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
ESP_LOGV(TAG, "Received packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
|
||||
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
|
||||
|
||||
@@ -71,7 +71,7 @@ bool ESPNowTransport::on_received(const ESPNowRecvInfo &info, const uint8_t *dat
|
||||
return false; // Allow other handlers to run
|
||||
}
|
||||
|
||||
bool ESPNowTransport::on_broadcasted(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
bool ESPNowTransport::on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) {
|
||||
ESP_LOGV(TAG, "Received broadcast packet of size %u from %02X:%02X:%02X:%02X:%02X:%02X", size, info.src_addr[0],
|
||||
info.src_addr[1], info.src_addr[2], info.src_addr[3], info.src_addr[4], info.src_addr[5]);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace espnow {
|
||||
class ESPNowTransport : public packet_transport::PacketTransport,
|
||||
public Parented<ESPNowComponent>,
|
||||
public ESPNowReceivedPacketHandler,
|
||||
public ESPNowBroadcastedHandler {
|
||||
public ESPNowBroadcastHandler {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
@@ -25,8 +25,8 @@ class ESPNowTransport : public packet_transport::PacketTransport,
|
||||
}
|
||||
|
||||
// ESPNow handler interface
|
||||
bool on_received(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
bool on_broadcasted(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
bool on_receive(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
bool on_broadcast(const ESPNowRecvInfo &info, const uint8_t *data, uint8_t size) override;
|
||||
|
||||
protected:
|
||||
void send_packet(const std::vector<uint8_t> &buf) const override;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "core.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/time_64.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
|
||||
@@ -15,32 +14,7 @@ void loop();
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
// Inline the tick read so esphome::millis() matches MillisInternal::get()'s fast
|
||||
// path instead of going through the Arduino core's out-of-line ::millis() wrapper.
|
||||
//
|
||||
// RTL87xx / LN882x (1 kHz): xTaskGetTickCount() is already ms. IRAM_ATTR + ISR
|
||||
// dispatch are needed because ISR handlers (e.g. rotary_encoder) call millis().
|
||||
//
|
||||
// BK72xx (500 Hz): ticks * portTICK_PERIOD_MS (== 2). IRAM_ATTR and ISR dispatch
|
||||
// are both unnecessary — the SDK masks FIQ + IRQ during flash writes (see hal.h),
|
||||
// so no ISR runs while flash is stalled.
|
||||
#if defined(USE_RTL87XX) || defined(USE_LN882X)
|
||||
uint32_t IRAM_ATTR HOT millis() {
|
||||
static_assert(configTICK_RATE_HZ == 1000, "millis() fast path requires 1 kHz FreeRTOS tick");
|
||||
return in_isr_context() ? xTaskGetTickCountFromISR() : xTaskGetTickCount();
|
||||
}
|
||||
#elif defined(USE_BK72XX)
|
||||
uint32_t HOT millis() {
|
||||
static_assert(configTICK_RATE_HZ == 500, "BK72xx millis() fast path assumes 500 Hz FreeRTOS tick");
|
||||
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
}
|
||||
#else
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
#endif
|
||||
uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h.
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
|
||||
|
||||
void arch_init() {
|
||||
|
||||
@@ -164,7 +164,7 @@ class RemoteTransmitterBase : public RemoteComponentBase {
|
||||
return TransmitCall(this);
|
||||
}
|
||||
template<typename Protocol>
|
||||
void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
|
||||
void transmit(const Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
|
||||
auto call = this->transmit();
|
||||
Protocol().encode(call.get_data(), data);
|
||||
call.set_send_times(send_times);
|
||||
@@ -250,10 +250,10 @@ template<typename T> class RemoteReceiverBinarySensor : public RemoteReceiverBin
|
||||
}
|
||||
|
||||
public:
|
||||
void set_data(typename T::ProtocolData data) { data_ = data; }
|
||||
void set_data(T::ProtocolData data) { data_ = data; }
|
||||
|
||||
protected:
|
||||
typename T::ProtocolData data_;
|
||||
T::ProtocolData data_;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -278,7 +278,7 @@ class RemoteTransmittable {
|
||||
|
||||
protected:
|
||||
template<typename Protocol>
|
||||
void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
|
||||
void transmit_(const Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
|
||||
this->transmitter_->transmit<Protocol>(data, send_times, send_wait);
|
||||
}
|
||||
RemoteTransmitterBase *transmitter_;
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void HOT yield() { ::yield(); }
|
||||
uint64_t millis_64() { return micros_to_millis<uint64_t>(time_us_64()); }
|
||||
uint32_t HOT millis() { return micros_to_millis(time_us_64()); }
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t HOT micros() { return ::micros(); }
|
||||
// yield(), delay(), micros(), millis(), millis_64() inlined in hal.h.
|
||||
void HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
watchdog_reboot(0, 0, 10);
|
||||
|
||||
@@ -39,9 +39,18 @@ 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 = (1U << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1U,
|
||||
.trigger_panic = true,
|
||||
.idle_core_mask = 0,
|
||||
.trigger_panic = false,
|
||||
};
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||
wdt_config.idle_core_mask |= (1U << 0U);
|
||||
#endif
|
||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
|
||||
wdt_config.idle_core_mask |= (1U << 1U);
|
||||
#endif
|
||||
#if CONFIG_ESP_TASK_WDT_PANIC
|
||||
wdt_config.trigger_panic = true;
|
||||
#endif
|
||||
esp_task_wdt_reconfigure(&wdt_config);
|
||||
#endif // USE_ESP32
|
||||
|
||||
|
||||
+8
-1
@@ -25,7 +25,10 @@ from esphome.const import (
|
||||
CONF_SUBSTITUTIONS,
|
||||
)
|
||||
from esphome.core import CORE, DocumentRange, EsphomeError
|
||||
import esphome.core.config as core_config
|
||||
|
||||
# `esphome.core.config` is imported lazily at its two use sites below.
|
||||
# It pulls in `esphome.automation` and `esphome.config_validation`, which
|
||||
# dominate `esphome.__main__` startup cost when loaded eagerly here.
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import indent
|
||||
from esphome.loader import ComponentManifest, get_component, get_platform
|
||||
@@ -968,6 +971,8 @@ class CoreFinalValidateStep(ConfigValidationStep):
|
||||
if result.errors:
|
||||
return
|
||||
|
||||
import esphome.core.config as core_config
|
||||
|
||||
token = fv.full_config.set(result)
|
||||
with result.catch_error([CONF_ESPHOME]):
|
||||
if CONF_ESPHOME in result:
|
||||
@@ -1073,6 +1078,8 @@ def validate_config(
|
||||
return result
|
||||
|
||||
# 2. Load partial core config
|
||||
import esphome.core.config as core_config
|
||||
|
||||
result[CONF_ESPHOME] = config[CONF_ESPHOME]
|
||||
result.add_output_path([CONF_ESPHOME], CONF_ESPHOME)
|
||||
try:
|
||||
|
||||
@@ -55,7 +55,7 @@ template<typename ValueType, int MaxBits> struct DefaultBitPolicy {
|
||||
///
|
||||
template<typename ValueType, typename BitPolicy = DefaultBitPolicy<ValueType, 16>> class FiniteSetMask {
|
||||
public:
|
||||
using bitmask_t = typename BitPolicy::mask_t;
|
||||
using bitmask_t = BitPolicy::mask_t;
|
||||
|
||||
constexpr FiniteSetMask() = default;
|
||||
|
||||
|
||||
+27
-104
@@ -2,125 +2,48 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/time_64.h"
|
||||
#include "esphome/core/time_conversion.h"
|
||||
|
||||
// Per-platform HAL bits (IRAM_ATTR / PROGMEM macros, in_isr_context(),
|
||||
// inline yield/delay/micros/millis/millis_64 wrappers, ESP8266 progmem
|
||||
// helpers) live under esphome/core/hal/ and are dispatched here based on
|
||||
// the active USE_* platform define. Each header guards its body with the
|
||||
// matching #ifdef USE_<platform> and re-enters namespace esphome {} so it
|
||||
// is safe to be re-included.
|
||||
#if defined(USE_ESP32)
|
||||
#include <esp_attr.h>
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
#include "esphome/core/hal/hal_esp32.h"
|
||||
#elif defined(USE_ESP8266)
|
||||
|
||||
#include <c_types.h>
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM ICACHE_RODATA_ATTR
|
||||
#endif
|
||||
|
||||
#elif defined(USE_RP2040)
|
||||
|
||||
#define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical")))
|
||||
#define PROGMEM
|
||||
|
||||
#include "esphome/core/hal/hal_esp8266.h"
|
||||
#elif defined(USE_LIBRETINY)
|
||||
|
||||
// IRAM_ATTR places a function in executable RAM so it is callable from an
|
||||
// ISR even while flash is busy (XIP stall, OTA, logger flash write).
|
||||
// Each family uses a section its stock linker already routes to RAM:
|
||||
// RTL8710B → .image2.ram.text, RTL8720C → .sram.text. LN882H is the
|
||||
// exception: its stock linker has no matching glob, so patch_linker.py
|
||||
// injects KEEP(*(.sram.text*)) into .flash_copysection at pre-link.
|
||||
//
|
||||
// BK72xx (all variants) are left as a no-op: their SDK wraps flash
|
||||
// operations in GLOBAL_INT_DISABLE() which masks FIQ + IRQ at the CPU for
|
||||
// the duration of every write, so no ISR fires while flash is stalled and
|
||||
// the race IRAM_ATTR guards against cannot occur. The trade-off is that
|
||||
// interrupts are delayed (not dropped) by up to ~20 ms during a sector
|
||||
// erase, but that is an SDK-level choice and cannot be changed from this
|
||||
// layer.
|
||||
#if defined(USE_BK72XX)
|
||||
#define IRAM_ATTR
|
||||
#elif defined(USE_LIBRETINY_VARIANT_RTL8710B)
|
||||
// Stock linker consumes *(.image2.ram.text*) into .ram_image2.text (> BD_RAM).
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".image2.ram.text")))
|
||||
#include "esphome/core/hal/hal_libretiny.h"
|
||||
#elif defined(USE_RP2040)
|
||||
#include "esphome/core/hal/hal_rp2040.h"
|
||||
#elif defined(USE_HOST)
|
||||
#include "esphome/core/hal/hal_host.h"
|
||||
#elif defined(USE_ZEPHYR)
|
||||
#include "esphome/core/hal/hal_zephyr.h"
|
||||
#else
|
||||
// RTL8720C: stock linker consumes *(.sram.text*) into .ram.code_text.
|
||||
// LN882H: patch_linker.py.script injects *(.sram.text*) into
|
||||
// .flash_copysection (> RAM0 AT> FLASH).
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
|
||||
#endif
|
||||
#define PROGMEM
|
||||
|
||||
#else
|
||||
|
||||
#define IRAM_ATTR
|
||||
#define PROGMEM
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
// Declared in the Beken FreeRTOS port (portmacro.h) and built in ARM mode so
|
||||
// it is callable from Thumb code via interworking. The MRS CPSR instruction
|
||||
// is ARM-only and user code here may be built in Thumb, so in_isr_context()
|
||||
// defers to this port helper on BK72xx instead of reading CPSR inline.
|
||||
extern "C" uint32_t platform_is_in_interrupt_context(void);
|
||||
#error "hal.h: not implemented for this platform"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
/// always_inline so callers placed in IRAM keep the detection in IRAM.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() {
|
||||
#if defined(USE_ESP32)
|
||||
return xPortInIsrContext() != 0;
|
||||
#elif defined(USE_ESP8266)
|
||||
// ESP8266 has no reliable single-register ISR detection: PS.INTLEVEL is
|
||||
// non-zero both in a real ISR and when user code masks interrupts. The
|
||||
// ESP8266 wake path is context-agnostic (wake_loop_impl uses esp_schedule
|
||||
// which is ISR-safe) so this helper is unused on this platform.
|
||||
return false;
|
||||
#elif defined(USE_RP2040)
|
||||
uint32_t ipsr;
|
||||
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||
return ipsr != 0;
|
||||
#elif defined(USE_BK72XX)
|
||||
// BK72xx is ARM968E-S (ARM9); see extern declaration above.
|
||||
return platform_is_in_interrupt_context() != 0;
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// Cortex-M (AmebaZ, AmebaZ2, LN882H). IPSR is the active exception number;
|
||||
// non-zero means we're in a handler.
|
||||
uint32_t ipsr;
|
||||
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||
return ipsr != 0;
|
||||
#else
|
||||
// Host and any future platform without an ISR concept.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void yield();
|
||||
uint32_t millis();
|
||||
uint64_t millis_64();
|
||||
uint32_t micros();
|
||||
void delay(uint32_t ms);
|
||||
// ESP8266 inlines delayMicroseconds() and arch_feed_wdt() in hal/hal_esp8266.h;
|
||||
// every other platform keeps them out-of-line in components/<platform>/core.cpp.
|
||||
#ifndef USE_ESP8266
|
||||
void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming)
|
||||
void arch_feed_wdt();
|
||||
#endif
|
||||
void __attribute__((noreturn)) arch_restart();
|
||||
void arch_init();
|
||||
void arch_feed_wdt();
|
||||
uint32_t arch_get_cpu_cycle_count();
|
||||
uint32_t arch_get_cpu_freq_hz();
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: pgm_read_* does real flash reads on Harvard architecture
|
||||
uint8_t progmem_read_byte(const uint8_t *addr);
|
||||
const char *progmem_read_ptr(const char *const *addr);
|
||||
uint16_t progmem_read_uint16(const uint16_t *addr);
|
||||
#else
|
||||
// All other platforms: PROGMEM is a no-op, so these are direct dereferences
|
||||
#ifndef USE_ESP8266
|
||||
// All non-ESP8266 platforms: PROGMEM is a no-op, so these are direct dereferences.
|
||||
// ESP8266's out-of-line declarations live in hal/hal_esp8266.h.
|
||||
inline uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
inline const char *progmem_read_ptr(const char *const *addr) { return *addr; }
|
||||
inline uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; }
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstdint>
|
||||
#include <esp_attr.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include "esphome/core/time_conversion.h"
|
||||
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() { return xPortInIsrContext() != 0; }
|
||||
|
||||
// Forward decl from <esp_timer.h>.
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
extern "C" int64_t esp_timer_get_time(void);
|
||||
|
||||
__attribute__((always_inline)) inline void yield() { vPortYield(); }
|
||||
__attribute__((always_inline)) inline void delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(esp_timer_get_time()); }
|
||||
uint32_t millis();
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() {
|
||||
return micros_to_millis<uint64_t>(static_cast<uint64_t>(esp_timer_get_time()));
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include <c_types.h>
|
||||
#include <cstdint>
|
||||
#include <pgmspace.h>
|
||||
|
||||
#include "esphome/core/time_64.h"
|
||||
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM ICACHE_RODATA_ATTR
|
||||
#endif
|
||||
|
||||
// Forward decls from Arduino's <Arduino.h> for the inline wrappers below.
|
||||
// NOLINTBEGIN(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
extern "C" void yield(void);
|
||||
extern "C" void delay(unsigned long ms);
|
||||
extern "C" unsigned long micros(void);
|
||||
extern "C" unsigned long millis(void);
|
||||
// NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
|
||||
// Forward decl from <user_interface.h> for arch_feed_wdt() inline below.
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
extern "C" void system_soft_wdt_feed(void);
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Forward decl from helpers.h so this header stays cheap.
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
void delay_microseconds_safe(uint32_t us);
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
/// ESP8266 has no reliable single-register ISR detection: PS.INTLEVEL is
|
||||
/// non-zero both in a real ISR and when user code masks interrupts. The
|
||||
/// ESP8266 wake path is context-agnostic (wake_loop_impl uses esp_schedule
|
||||
/// which is ISR-safe) so this helper is unused on this platform.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() { return false; }
|
||||
|
||||
__attribute__((always_inline)) inline void yield() { ::yield(); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(::micros()); }
|
||||
void delay(uint32_t ms);
|
||||
uint32_t millis();
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
|
||||
// ESP8266: pgm_read_* does aligned 32-bit flash reads on Harvard architecture.
|
||||
// Inline-forward to the platform macros so the wrappers themselves don't
|
||||
// occupy IRAM/flash on every call site.
|
||||
__attribute__((always_inline)) inline uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||
return pgm_read_byte(addr); // NOLINT
|
||||
}
|
||||
__attribute__((always_inline)) inline const char *progmem_read_ptr(const char *const *addr) {
|
||||
return reinterpret_cast<const char *>(pgm_read_ptr(addr)); // NOLINT
|
||||
}
|
||||
__attribute__((always_inline)) inline uint16_t progmem_read_uint16(const uint16_t *addr) {
|
||||
return pgm_read_word(addr); // NOLINT
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
__attribute__((always_inline)) inline void delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
__attribute__((always_inline)) inline void arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP8266
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define IRAM_ATTR
|
||||
#define PROGMEM
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
/// Host has no ISR concept.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() { return false; }
|
||||
|
||||
void yield();
|
||||
void delay(uint32_t ms);
|
||||
uint32_t micros();
|
||||
uint32_t millis();
|
||||
uint64_t millis_64();
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// For the inline millis() fast paths (xTaskGetTickCount, portTICK_PERIOD_MS).
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
#include "esphome/core/time_64.h"
|
||||
|
||||
// IRAM_ATTR places a function in executable RAM so it is callable from an
|
||||
// ISR even while flash is busy (XIP stall, OTA, logger flash write).
|
||||
// Each family uses a section its stock linker already routes to RAM:
|
||||
// RTL8710B → .image2.ram.text, RTL8720C → .sram.text. LN882H is the
|
||||
// exception: its stock linker has no matching glob, so patch_linker.py
|
||||
// injects KEEP(*(.sram.text*)) into .flash_copysection at pre-link.
|
||||
//
|
||||
// BK72xx (all variants) are left as a no-op: their SDK wraps flash
|
||||
// operations in GLOBAL_INT_DISABLE() which masks FIQ + IRQ at the CPU for
|
||||
// the duration of every write, so no ISR fires while flash is stalled and
|
||||
// the race IRAM_ATTR guards against cannot occur. The trade-off is that
|
||||
// interrupts are delayed (not dropped) by up to ~20 ms during a sector
|
||||
// erase, but that is an SDK-level choice and cannot be changed from this
|
||||
// layer.
|
||||
#if defined(USE_BK72XX)
|
||||
#define IRAM_ATTR
|
||||
#elif defined(USE_LIBRETINY_VARIANT_RTL8710B)
|
||||
// Stock linker consumes *(.image2.ram.text*) into .ram_image2.text (> BD_RAM).
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".image2.ram.text")))
|
||||
#else
|
||||
// RTL8720C: stock linker consumes *(.sram.text*) into .ram.code_text.
|
||||
// LN882H: patch_linker.py.script injects *(.sram.text*) into
|
||||
// .flash_copysection (> RAM0 AT> FLASH).
|
||||
#define IRAM_ATTR __attribute__((noinline, section(".sram.text")))
|
||||
#endif
|
||||
#define PROGMEM
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
// Declared in the Beken FreeRTOS port (portmacro.h) and built in ARM mode so
|
||||
// it is callable from Thumb code via interworking. The MRS CPSR instruction
|
||||
// is ARM-only and user code here may be built in Thumb, so in_isr_context()
|
||||
// defers to this port helper on BK72xx instead of reading CPSR inline.
|
||||
extern "C" uint32_t platform_is_in_interrupt_context(void);
|
||||
#endif
|
||||
|
||||
// Forward decls from Arduino's <Arduino.h> for the inline wrappers below.
|
||||
// NOLINTBEGIN(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
extern "C" void yield(void);
|
||||
extern "C" void delay(unsigned long ms);
|
||||
extern "C" unsigned long micros(void);
|
||||
extern "C" unsigned long millis(void);
|
||||
// NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() {
|
||||
#if defined(USE_BK72XX)
|
||||
// BK72xx is ARM968E-S (ARM9); see extern declaration above.
|
||||
return platform_is_in_interrupt_context() != 0;
|
||||
#else
|
||||
// Cortex-M (AmebaZ, AmebaZ2, LN882H). IPSR is the active exception number;
|
||||
// non-zero means we're in a handler.
|
||||
uint32_t ipsr;
|
||||
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||
return ipsr != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline void yield() { ::yield(); }
|
||||
__attribute__((always_inline)) inline void delay(uint32_t ms) { ::delay(ms); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(::micros()); }
|
||||
|
||||
// Per-variant millis() fast path — matches MillisInternal::get().
|
||||
#if defined(USE_RTL87XX) || defined(USE_LN882X)
|
||||
static_assert(configTICK_RATE_HZ == 1000, "millis() fast path requires 1 kHz FreeRTOS tick");
|
||||
__attribute__((always_inline)) inline uint32_t millis() {
|
||||
// xTaskGetTickCountFromISR is mandatory in interrupt context per the FreeRTOS API contract.
|
||||
return in_isr_context() ? xTaskGetTickCountFromISR() : xTaskGetTickCount();
|
||||
}
|
||||
#elif defined(USE_BK72XX)
|
||||
static_assert(configTICK_RATE_HZ == 500, "BK72xx millis() fast path assumes 500 Hz FreeRTOS tick");
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return xTaskGetTickCount() * portTICK_PERIOD_MS; }
|
||||
#else
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return static_cast<uint32_t>(::millis()); }
|
||||
#endif
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return Millis64Impl::compute(millis()); }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_RP2040
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "esphome/core/time_conversion.h"
|
||||
|
||||
#define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical")))
|
||||
#define PROGMEM
|
||||
|
||||
// Forward decls from Arduino's <Arduino.h> for the inline wrappers below.
|
||||
// NOLINTBEGIN(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
extern "C" void yield(void);
|
||||
extern "C" void delay(unsigned long ms);
|
||||
extern "C" unsigned long micros(void);
|
||||
extern "C" unsigned long millis(void);
|
||||
// NOLINTEND(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
|
||||
// Forward decl from <pico/time.h>.
|
||||
extern "C" uint64_t time_us_64(void);
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() {
|
||||
uint32_t ipsr;
|
||||
__asm__ volatile("mrs %0, ipsr" : "=r"(ipsr));
|
||||
return ipsr != 0;
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) inline void yield() { ::yield(); }
|
||||
__attribute__((always_inline)) inline void delay(uint32_t ms) { ::delay(ms); }
|
||||
__attribute__((always_inline)) inline uint32_t micros() { return static_cast<uint32_t>(::micros()); }
|
||||
__attribute__((always_inline)) inline uint32_t millis() { return micros_to_millis(::time_us_64()); }
|
||||
__attribute__((always_inline)) inline uint64_t millis_64() { return micros_to_millis<uint64_t>(::time_us_64()); }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_RP2040
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define IRAM_ATTR
|
||||
#define PROGMEM
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Returns true when executing inside an interrupt handler.
|
||||
/// Zephyr/nRF52: not currently consulted — wake path is platform-specific.
|
||||
__attribute__((always_inline)) inline bool in_isr_context() { return false; }
|
||||
|
||||
void yield();
|
||||
void delay(uint32_t ms);
|
||||
uint32_t micros();
|
||||
uint32_t millis();
|
||||
uint64_t millis_64();
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ZEPHYR
|
||||
+4
-37
@@ -20,6 +20,7 @@
|
||||
#include <strings.h>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/time_conversion.h"
|
||||
|
||||
// Backward compatibility re-export of heap-allocating helpers.
|
||||
// These functions have moved to alloc_helpers.h. External components should
|
||||
@@ -833,43 +834,9 @@ template<std::integral T> constexpr uint32_t fnv1a_hash_extend(uint32_t hash, T
|
||||
constexpr uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); }
|
||||
inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); }
|
||||
|
||||
/// Convert a 64-bit microsecond count to milliseconds without calling
|
||||
/// __udivdi3 (software 64-bit divide, ~1200 ns on Xtensa @ 240 MHz).
|
||||
///
|
||||
/// Returns uint32_t by default (for millis()), or uint64_t when requested
|
||||
/// (for millis_64()). The only difference is whether hi * Q is truncated
|
||||
/// to 32 bits or widened to 64.
|
||||
///
|
||||
/// On 32-bit targets, GCC does not optimize 64-bit constant division into a
|
||||
/// multiply-by-reciprocal. Since 1000 = 8 * 125, we first right-shift by 3
|
||||
/// (free divide-by-8), then use the Euclidean division identity to decompose
|
||||
/// the remaining 64-bit divide-by-125 into a single 32-bit division:
|
||||
///
|
||||
/// floor(us / 1000) = floor(floor(us / 8) / 125) [exact for integers]
|
||||
/// 2^32 = Q * 125 + R (34359738 * 125 + 46)
|
||||
/// (hi * 2^32 + lo) / 125 = hi * Q + (hi * R + lo) / 125
|
||||
///
|
||||
/// GCC optimizes the remaining 32-bit "/ 125U" into a multiply-by-reciprocal
|
||||
/// (mulhu + shift), so no division instruction is emitted.
|
||||
///
|
||||
/// Safe for us up to ~3.2e18 (~101,700 years of microseconds).
|
||||
///
|
||||
/// See: https://en.wikipedia.org/wiki/Euclidean_division
|
||||
/// See: https://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
|
||||
template<typename ReturnT = uint32_t> inline constexpr ESPHOME_ALWAYS_INLINE ReturnT micros_to_millis(uint64_t us) {
|
||||
constexpr uint32_t d = 125U;
|
||||
constexpr uint32_t q = static_cast<uint32_t>((1ULL << 32) / d); // 34359738
|
||||
constexpr uint32_t r = static_cast<uint32_t>((1ULL << 32) % d); // 46
|
||||
// 1000 = 8 * 125; divide-by-8 is a free shift
|
||||
uint64_t x = us >> 3;
|
||||
uint32_t lo = static_cast<uint32_t>(x);
|
||||
uint32_t hi = static_cast<uint32_t>(x >> 32);
|
||||
// Combine remainder term: hi * (2^32 % 125) + lo
|
||||
uint32_t adj = hi * r + lo;
|
||||
// If adj overflowed, the true value is 2^32 + adj; apply the identity again
|
||||
// static_cast<ReturnT>(hi) widens to 64-bit when ReturnT=uint64_t, preserving upper bits of hi*q
|
||||
return static_cast<ReturnT>(hi) * q + (adj < lo ? (adj + r) / d + q : adj / d);
|
||||
}
|
||||
// micros_to_millis<>() lives in its own lightweight header so hal.h can pull it
|
||||
// in for inline millis_64() without forcing every TU that includes hal.h to
|
||||
// also include the rest of helpers.h.
|
||||
|
||||
/// Return a random 32-bit unsigned integer.
|
||||
/// Not thread-safe. Must only be called from the main loop.
|
||||
|
||||
@@ -35,7 +35,9 @@ static constexpr uint32_t MAX_INTERVAL_DELAY = 5000;
|
||||
// Uses a stack buffer to avoid heap allocation
|
||||
// Uses ESPHOME_snprintf_P/ESPHOME_PSTR for ESP8266 to keep format strings in flash
|
||||
struct SchedulerNameLog {
|
||||
char buffer[20]; // Enough for "id:4294967295" or "hash:0xFFFFFFFF" or "(null)"
|
||||
// Sized for the widest formatted output: "self:0x" + 16 hex digits (64-bit pointer) + nul.
|
||||
// Also covers "id:4294967295", "hash:0xFFFFFFFF", "iid:4294967295", "(null)".
|
||||
char buffer[28];
|
||||
|
||||
// Format a scheduler item name for logging
|
||||
// Returns pointer to formatted string (either static_name or internal buffer)
|
||||
@@ -53,9 +55,15 @@ struct SchedulerNameLog {
|
||||
} else if (name_type == NameType::NUMERIC_ID) {
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("id:%" PRIu32), hash_or_id);
|
||||
return buffer;
|
||||
} else { // NUMERIC_ID_INTERNAL
|
||||
} else if (name_type == NameType::NUMERIC_ID_INTERNAL) {
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("iid:%" PRIu32), hash_or_id);
|
||||
return buffer;
|
||||
} else { // SELF_POINTER
|
||||
// static_name carries the void* key for SELF_POINTER (pointer-width union slot).
|
||||
// %p is specified as void* (not const void*), so strip const for the varargs call.
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("self:%p"),
|
||||
const_cast<void *>(static_cast<const void *>(static_name)));
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -293,6 +301,27 @@ bool HOT Scheduler::cancel_interval(Component *component, uint32_t id) {
|
||||
return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::INTERVAL);
|
||||
}
|
||||
|
||||
// Self-keyed scheduler API. The cancellation key is `self` (typically the caller's `this`),
|
||||
// passed through the existing static_name pointer slot. Matching is by raw pointer equality
|
||||
// (see matches_item_locked_'s SELF_POINTER branch). No Component pointer is stored, so
|
||||
// is_failed() skip and component-based log attribution don't apply.
|
||||
void HOT Scheduler::set_timeout(const void *self, uint32_t timeout, std::function<void()> &&func) {
|
||||
this->set_timer_common_(nullptr, SchedulerItem::TIMEOUT, NameType::SELF_POINTER, static_cast<const char *>(self), 0,
|
||||
timeout, std::move(func));
|
||||
}
|
||||
void HOT Scheduler::set_interval(const void *self, uint32_t interval, std::function<void()> &&func) {
|
||||
this->set_timer_common_(nullptr, SchedulerItem::INTERVAL, NameType::SELF_POINTER, static_cast<const char *>(self), 0,
|
||||
interval, std::move(func));
|
||||
}
|
||||
bool HOT Scheduler::cancel_timeout(const void *self) {
|
||||
return this->cancel_item_(nullptr, NameType::SELF_POINTER, static_cast<const char *>(self), 0,
|
||||
SchedulerItem::TIMEOUT);
|
||||
}
|
||||
bool HOT Scheduler::cancel_interval(const void *self) {
|
||||
return this->cancel_item_(nullptr, NameType::SELF_POINTER, static_cast<const char *>(self), 0,
|
||||
SchedulerItem::INTERVAL);
|
||||
}
|
||||
|
||||
// Suppress deprecation warnings for RetryResult usage in the still-present (but deprecated) retry implementation.
|
||||
// Remove before 2026.8.0 along with all retry code.
|
||||
#pragma GCC diagnostic push
|
||||
|
||||
+56
-24
@@ -146,22 +146,43 @@ class Scheduler {
|
||||
}
|
||||
|
||||
// Name storage type discriminator for SchedulerItem
|
||||
// Used to distinguish between static strings, hashed strings, numeric IDs, and internal numeric IDs
|
||||
// Used to distinguish between static strings, hashed strings, numeric IDs, internal numeric IDs,
|
||||
// and self-keyed pointers (caller-supplied `void *`, typically `this`).
|
||||
enum class NameType : uint8_t {
|
||||
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
||||
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
||||
NUMERIC_ID = 2, // uint32_t numeric identifier (component-level)
|
||||
NUMERIC_ID_INTERNAL = 3 // uint32_t numeric identifier (core/internal, separate namespace)
|
||||
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
||||
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
||||
NUMERIC_ID = 2, // uint32_t numeric identifier (component-level)
|
||||
NUMERIC_ID_INTERNAL = 3, // uint32_t numeric identifier (core/internal, separate namespace)
|
||||
SELF_POINTER = 4 // void* caller-supplied key (typically `this`); pointer equality
|
||||
};
|
||||
|
||||
/** Self-keyed timeout. The cancellation key is `self` (typically the caller's `this`).
|
||||
*
|
||||
* Use this when the caller schedules at most one timer of a single purpose at a time and
|
||||
* does not need a `Component` for `is_failed()` skip or log source attribution. Lets
|
||||
* small classes drop `Component` inheritance entirely when their only Component dependency
|
||||
* was the per-instance scheduler key.
|
||||
*
|
||||
* NOT applied for self-keyed items:
|
||||
* - `is_failed()` skip — callbacks always fire (no Component to consult).
|
||||
* - Log source attribution — logs use a generic "self:0x…" label.
|
||||
*
|
||||
* If you need either of those, use the existing `(Component *, id)` overloads.
|
||||
*/
|
||||
void set_timeout(const void *self, uint32_t timeout, std::function<void()> &&func);
|
||||
/// Self-keyed interval. See set_timeout(const void *, ...) for semantics.
|
||||
void set_interval(const void *self, uint32_t interval, std::function<void()> &&func);
|
||||
bool cancel_timeout(const void *self);
|
||||
bool cancel_interval(const void *self);
|
||||
|
||||
protected:
|
||||
struct SchedulerItem {
|
||||
// Ordered by size to minimize padding
|
||||
Component *component;
|
||||
// Optimized name storage using tagged union - zero heap allocation
|
||||
union {
|
||||
const char *static_name; // For STATIC_STRING (string literals, no allocation)
|
||||
uint32_t hash_or_id; // For HASHED_STRING or NUMERIC_ID
|
||||
const char *static_name; // For STATIC_STRING (string literals) and SELF_POINTER (caller's `this`)
|
||||
uint32_t hash_or_id; // For HASHED_STRING, NUMERIC_ID, and NUMERIC_ID_INTERNAL
|
||||
} name_;
|
||||
uint32_t interval;
|
||||
// Split time to handle millis() rollover. The scheduler combines the 32-bit millis()
|
||||
@@ -182,19 +203,19 @@ class Scheduler {
|
||||
// std::atomic<uint8_t> inlines correctly on all platforms.
|
||||
std::atomic<uint8_t> remove{0};
|
||||
|
||||
// Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||
bool is_retry : 1; // True if this is a retry timeout
|
||||
// 4 bits padding
|
||||
#else
|
||||
// Single-threaded or multi-threaded without atomics: can pack all fields together
|
||||
// Bit-packed fields (5 bits used, 3 bits padding in 1 byte)
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||
bool remove : 1;
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||
NameType name_type_ : 3; // Discriminator for name_ union (0–4, see NameType enum)
|
||||
bool is_retry : 1; // True if this is a retry timeout
|
||||
// 3 bits padding
|
||||
#else
|
||||
// Single-threaded or multi-threaded without atomics: can pack all fields together
|
||||
// Bit-packed fields (6 bits used, 2 bits padding in 1 byte)
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||
bool remove : 1;
|
||||
NameType name_type_ : 3; // Discriminator for name_ union (0–4, see NameType enum)
|
||||
bool is_retry : 1; // True if this is a retry timeout
|
||||
// 2 bits padding
|
||||
#endif
|
||||
|
||||
// Constructor
|
||||
@@ -228,19 +249,26 @@ class Scheduler {
|
||||
SchedulerItem(SchedulerItem &&) = delete;
|
||||
SchedulerItem &operator=(SchedulerItem &&) = delete;
|
||||
|
||||
// Helper to get the static name (only valid for STATIC_STRING type)
|
||||
const char *get_name() const { return (name_type_ == NameType::STATIC_STRING) ? name_.static_name : nullptr; }
|
||||
// Helper to get the pointer-slot value (valid for STATIC_STRING and SELF_POINTER types).
|
||||
// Both share the same union member, so callers (e.g. log formatters) can read either uniformly.
|
||||
const char *get_name() const {
|
||||
return (name_type_ == NameType::STATIC_STRING || name_type_ == NameType::SELF_POINTER) ? name_.static_name
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
// Helper to get the hash or numeric ID (only valid for HASHED_STRING or NUMERIC_ID types)
|
||||
uint32_t get_name_hash_or_id() const { return (name_type_ != NameType::STATIC_STRING) ? name_.hash_or_id : 0; }
|
||||
// Helper to get the hash or numeric ID (only valid for HASHED_STRING / NUMERIC_ID / NUMERIC_ID_INTERNAL types)
|
||||
uint32_t get_name_hash_or_id() const {
|
||||
return (name_type_ != NameType::STATIC_STRING && name_type_ != NameType::SELF_POINTER) ? name_.hash_or_id : 0;
|
||||
}
|
||||
|
||||
// Helper to get the name type
|
||||
NameType get_name_type() const { return name_type_; }
|
||||
|
||||
// Set name storage: for STATIC_STRING stores the pointer, for all other types stores hash_or_id.
|
||||
// Both union members occupy the same offset, so only one store is needed.
|
||||
// Set name storage. STATIC_STRING/SELF_POINTER use the static_name pointer slot
|
||||
// (both are pointer-width); other types use hash_or_id. Both union members occupy
|
||||
// the same offset, so only one store is needed.
|
||||
void set_name(NameType type, const char *static_name, uint32_t hash_or_id) {
|
||||
if (type == NameType::STATIC_STRING) {
|
||||
if (type == NameType::STATIC_STRING || type == NameType::SELF_POINTER) {
|
||||
name_.static_name = static_name;
|
||||
} else {
|
||||
name_.hash_or_id = hash_or_id;
|
||||
@@ -367,10 +395,14 @@ class Scheduler {
|
||||
// Name type must match
|
||||
if (item->get_name_type() != name_type)
|
||||
return false;
|
||||
// For static strings, compare the string content; for hash/ID, compare the value
|
||||
// STATIC_STRING: compare string content. SELF_POINTER: raw pointer equality (no strcmp).
|
||||
// Other types: compare hash/ID value.
|
||||
if (name_type == NameType::STATIC_STRING) {
|
||||
return this->names_match_static_(item->get_name(), static_name);
|
||||
}
|
||||
if (name_type == NameType::SELF_POINTER) {
|
||||
return item->name_.static_name == static_name;
|
||||
}
|
||||
return item->get_name_hash_or_id() == hash_or_id;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Scheduler;
|
||||
@@ -24,7 +22,9 @@ class Millis64Impl {
|
||||
static uint32_t last_millis;
|
||||
static uint16_t millis_major;
|
||||
|
||||
static inline uint64_t ESPHOME_ALWAYS_INLINE compute(uint32_t now) {
|
||||
// Raw __attribute__((always_inline)) (not ESPHOME_ALWAYS_INLINE) so this
|
||||
// header does not need to pull helpers.h.
|
||||
static inline uint64_t __attribute__((always_inline)) compute(uint32_t now) {
|
||||
// Half the 32-bit range - used to detect rollovers vs normal time progression
|
||||
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Convert a 64-bit microsecond count to milliseconds without calling
|
||||
/// __udivdi3 (software 64-bit divide, ~1200 ns on Xtensa @ 240 MHz).
|
||||
///
|
||||
/// Returns uint32_t by default (for millis()), or uint64_t when requested
|
||||
/// (for millis_64()). The only difference is whether hi * Q is truncated
|
||||
/// to 32 bits or widened to 64.
|
||||
///
|
||||
/// On 32-bit targets, GCC does not optimize 64-bit constant division into a
|
||||
/// multiply-by-reciprocal. Since 1000 = 8 * 125, we first right-shift by 3
|
||||
/// (free divide-by-8), then use the Euclidean division identity to decompose
|
||||
/// the remaining 64-bit divide-by-125 into a single 32-bit division:
|
||||
///
|
||||
/// floor(us / 1000) = floor(floor(us / 8) / 125) [exact for integers]
|
||||
/// 2^32 = Q * 125 + R (34359738 * 125 + 46)
|
||||
/// (hi * 2^32 + lo) / 125 = hi * Q + (hi * R + lo) / 125
|
||||
///
|
||||
/// GCC optimizes the remaining 32-bit "/ 125U" into a multiply-by-reciprocal
|
||||
/// (mulhu + shift), so no division instruction is emitted.
|
||||
///
|
||||
/// Safe for us up to ~3.2e18 (~101,700 years of microseconds).
|
||||
///
|
||||
/// See: https://en.wikipedia.org/wiki/Euclidean_division
|
||||
/// See: https://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html
|
||||
template<typename ReturnT = uint32_t>
|
||||
__attribute__((always_inline)) inline constexpr ReturnT micros_to_millis(uint64_t us) {
|
||||
constexpr uint32_t d = 125U;
|
||||
constexpr uint32_t q = static_cast<uint32_t>((1ULL << 32) / d); // 34359738
|
||||
constexpr uint32_t r = static_cast<uint32_t>((1ULL << 32) % d); // 46
|
||||
// 1000 = 8 * 125; divide-by-8 is a free shift
|
||||
uint64_t x = us >> 3;
|
||||
uint32_t lo = static_cast<uint32_t>(x);
|
||||
uint32_t hi = static_cast<uint32_t>(x >> 32);
|
||||
// Combine remainder term: hi * (2^32 % 125) + lo
|
||||
uint32_t adj = hi * r + lo;
|
||||
// If adj overflowed, the true value is 2^32 + adj; apply the identity again
|
||||
// static_cast<ReturnT>(hi) widens to 64-bit when ReturnT=uint64_t, preserving upper bits of hi*q
|
||||
return static_cast<ReturnT>(hi) * q + (adj < lo ? (adj + r) / d + q : adj / d);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -175,6 +175,11 @@ def download_content(url: str, path: Path, timeout: int = NETWORK_TIMEOUT) -> by
|
||||
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
|
||||
)
|
||||
req.raise_for_status()
|
||||
# `.content` reads the body lazily; chunked-decode, gzip-decode,
|
||||
# and mid-stream connection errors all surface here as
|
||||
# RequestException subclasses, so this needs the same fall-back
|
||||
# treatment as the request itself.
|
||||
data = req.content
|
||||
except requests.exceptions.RequestException as e:
|
||||
if path.exists():
|
||||
_LOGGER.warning(
|
||||
@@ -185,7 +190,6 @@ def download_content(url: str, path: Path, timeout: int = NETWORK_TIMEOUT) -> by
|
||||
return path.read_bytes()
|
||||
raise cv.Invalid(f"Could not download from {url}: {e}") from e
|
||||
|
||||
data = req.content
|
||||
write_file(path, data)
|
||||
_write_etag(path, req.headers.get(ETAG))
|
||||
return data
|
||||
|
||||
+20
-7
@@ -9,14 +9,23 @@ 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
|
||||
import esphome.core.config
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
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__)
|
||||
|
||||
|
||||
@@ -94,7 +103,7 @@ class ComponentManifest:
|
||||
return getattr(self.module, "CODEOWNERS", [])
|
||||
|
||||
@property
|
||||
def instance_type(self) -> MockObjClass | None:
|
||||
def instance_type(self) -> "MockObjClass | None":
|
||||
return getattr(self.module, "INSTANCE_TYPE", None)
|
||||
|
||||
@property
|
||||
@@ -213,6 +222,13 @@ def _lookup_module(domain: str, exception: bool) -> ComponentManifest | None:
|
||||
if domain in _COMPONENT_CACHE:
|
||||
return _COMPONENT_CACHE[domain]
|
||||
|
||||
if domain == "esphome":
|
||||
import esphome.core.config
|
||||
|
||||
manif = ComponentManifest(esphome.core.config, recursive_sources=True)
|
||||
_COMPONENT_CACHE[domain] = manif
|
||||
return manif
|
||||
|
||||
try:
|
||||
module = importlib.import_module(f"esphome.components.{domain}")
|
||||
except ImportError as e:
|
||||
@@ -248,9 +264,6 @@ def get_platform(domain: str, platform: str) -> ComponentManifest | None:
|
||||
|
||||
_COMPONENT_CACHE: dict[str, ComponentManifest] = {}
|
||||
CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve()
|
||||
_COMPONENT_CACHE["esphome"] = ComponentManifest(
|
||||
esphome.core.config, recursive_sources=True
|
||||
)
|
||||
|
||||
|
||||
def _replace_component_manifest(domain: str, manifest: ComponentManifest) -> None:
|
||||
|
||||
Reference in New Issue
Block a user