mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac5bc6514 | ||
|
|
4941bc2d36 | ||
|
|
09667bb79c | ||
|
|
8915d06b05 | ||
|
|
7df9b5ea2f | ||
|
|
a8ddb2535e | ||
|
|
d4922fb446 | ||
|
|
7acbe82c27 | ||
|
|
1a0b43449e | ||
|
|
860d819e33 | ||
|
|
f8f25c1ad4 | ||
|
|
22536f9338 | ||
|
|
c64a7fb678 | ||
|
|
6793dad8ed | ||
|
|
3dd9a28ed2 | ||
|
|
5a93de82bf |
No files matched your search
@@ -45,7 +45,6 @@ from .const import (
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
FAMILY_RTL8710B,
|
||||
FAMILY_RTL8720C,
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT,
|
||||
KEY_COMPONENT_DATA,
|
||||
@@ -60,8 +59,6 @@ from .const import (
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
# Fault handlers the crash handler wraps (BK7238, BK7231N)
|
||||
BK_TRAP_HANDLERS = ("bk_trap_udef", "bk_trap_pabt", "bk_trap_dabt")
|
||||
# Families with a crash handler; the RTL8720C installs it at run time (no wraps).
|
||||
CRASH_HANDLER_FAMILIES = (FAMILY_BK7231N, FAMILY_BK7238, FAMILY_RTL8720C)
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["preferences"]
|
||||
@@ -557,11 +554,10 @@ async def component_to_code(config):
|
||||
# RAM-executable output section and prints a post-link placement summary.
|
||||
if FAMILY_COMPONENT[config[CONF_FAMILY]] != COMPONENT_BK72XX:
|
||||
cg.add_platformio_option("extra_scripts", ["pre:patch_linker.py"])
|
||||
if config[CONF_FAMILY] in CRASH_HANDLER_FAMILIES:
|
||||
# Crash handler (crash_handler.cpp)
|
||||
if config[CONF_FAMILY] in (FAMILY_BK7231N, FAMILY_BK7238):
|
||||
# Crash handler (crash_handler.cpp, patch_noinit.py.script)
|
||||
cg.add_define("USE_LIBRETINY_CRASH_HANDLER")
|
||||
cg.add_define("USE_CRASH_HANDLER")
|
||||
if config[CONF_FAMILY] in (FAMILY_BK7231N, FAMILY_BK7238):
|
||||
for trap in BK_TRAP_HANDLERS:
|
||||
cg.add_build_flag(f"-Wl,--wrap={trap}")
|
||||
cg.add_platformio_option("extra_scripts", ["pre:patch_noinit.py"])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_LIBRETINY
|
||||
#ifdef USE_BK72XX
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LIBRETINY_CRASH_HANDLER
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
extern "C" {
|
||||
#include <arm_mcu_pub.h> // MCU_REG_BACKUP_SP_*
|
||||
#include <intc.h> // struct arm_registers
|
||||
@@ -40,19 +39,6 @@ extern char _itcmcode_ram_begin[];
|
||||
extern char _itcmcode_ram_end[];
|
||||
}
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
#endif // USE_BK72XX
|
||||
|
||||
#ifdef USE_RTL87XX
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
extern "C" {
|
||||
// Bounds of the code in flash (XIP) and in RAM (linker symbols, rtl8710c_ram.ld).
|
||||
extern char __xip_code_text_start__[];
|
||||
extern char __xip_code_text_end__[];
|
||||
extern char __ram_entry_text_start__[];
|
||||
extern char __ram_code_text_end__[];
|
||||
}
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
#endif // USE_RTL87XX
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
@@ -61,39 +47,50 @@ ESPHOME_LOG_TAG(TAG, "libretiny.crash");
|
||||
// 0xDEAD sentinel, then the record layout version.
|
||||
static constexpr uint32_t CRASH_MAGIC_V1 = 0xDEAD0001;
|
||||
|
||||
// BK72xx: the ARM9 exception taken.
|
||||
static constexpr uint32_t CRASH_REASON_UNDEFINED = 1;
|
||||
static constexpr uint32_t CRASH_REASON_PREFETCH_ABORT = 2;
|
||||
static constexpr uint32_t CRASH_REASON_DATA_ABORT = 3;
|
||||
// RTL87xx: the Cortex-M exception number (3 to 7) plus this offset.
|
||||
static constexpr uint32_t CRASH_REASON_CORTEX_M_OFFSET = 1;
|
||||
PROGMEM_STRING_TABLE(CrashReasonStrings, "Unknown", "Undefined instruction", "Prefetch abort", "Data abort",
|
||||
"Hard fault", "MemManage fault", "Bus fault", "Usage fault", "Secure fault");
|
||||
PROGMEM_STRING_TABLE(CrashReasonStrings, "Unknown", "Undefined instruction", "Prefetch abort", "Data abort");
|
||||
|
||||
// With Wi-Fi up the boot-time watchdog no longer fires: any interrupts-off spin hangs until power is
|
||||
// cycled (BK7238 and BK7231N; cause not in the open SDK). Re-armed here, long enough for the SDK's
|
||||
// register dump when sdk_silent is off.
|
||||
static constexpr uint32_t CRASH_REBOOT_WDT_MS = 500;
|
||||
|
||||
static constexpr size_t MAX_BACKTRACE = 8;
|
||||
// Words scanned above sp for return addresses.
|
||||
static constexpr size_t STACK_SCAN_WORDS = 64;
|
||||
|
||||
// SRAM: 192 KB on the BK7231N, 256 KB on the BK7238.
|
||||
static constexpr uint32_t SRAM_START = 0x00400000;
|
||||
#ifdef USE_LIBRETINY_VARIANT_BK7231N
|
||||
static constexpr uint32_t SRAM_END = 0x00430000;
|
||||
#else
|
||||
static constexpr uint32_t SRAM_END = 0x00440000;
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t CPSR_MODE_MASK = 0x1F;
|
||||
static constexpr uint32_t CPSR_MODE_USR = 0x10;
|
||||
static constexpr uint32_t CPSR_MODE_FIQ = 0x11;
|
||||
static constexpr uint32_t CPSR_MODE_IRQ = 0x12;
|
||||
static constexpr uint32_t CPSR_MODE_SVC = 0x13;
|
||||
static constexpr uint32_t CPSR_MODE_ABT = 0x17;
|
||||
static constexpr uint32_t CPSR_MODE_UND = 0x1B;
|
||||
static constexpr uint32_t CPSR_MODE_SYS = 0x1F;
|
||||
static constexpr uint32_t CPSR_THUMB = 1u << 5;
|
||||
|
||||
struct CrashRecord {
|
||||
uint32_t magic;
|
||||
uint32_t reason;
|
||||
uint32_t pc;
|
||||
uint32_t lr;
|
||||
uint32_t sp;
|
||||
uint32_t fault_status; // Cortex-M CFSR, 0 on BK72xx
|
||||
uint32_t fault_address; // Cortex-M BFAR or MMFAR when valid, else 0
|
||||
uint32_t backtrace[MAX_BACKTRACE];
|
||||
};
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
// Kept in ITCM, see patch_noinit.py.script.
|
||||
#define CRASH_RECORD_SECTION ".noinit"
|
||||
#else
|
||||
// rtl8710c_ram.ld keeps .ram.bss.noinit* out of the startup zeroing.
|
||||
#define CRASH_RECORD_SECTION ".ram.bss.noinit.esphome_crash"
|
||||
#endif
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
__attribute__((section(CRASH_RECORD_SECTION))) static CrashRecord s_record;
|
||||
__attribute__((section(".noinit"))) static CrashRecord s_record;
|
||||
|
||||
static bool s_record_read = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool s_record_valid = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -115,9 +112,6 @@ void crash_handler_log() {
|
||||
|
||||
ESP_LOGE(TAG, "*** CRASH DETECTED ON PREVIOUS BOOT ***");
|
||||
ESP_LOGE(TAG, " Reason: %s", LOG_STR_ARG(CrashReasonStrings::get_log_str(s_record.reason, 0)));
|
||||
if (s_record.fault_status != 0) {
|
||||
ESP_LOGE(TAG, " CFSR: 0x%08" PRIX32 " Address: 0x%08" PRIX32, s_record.fault_status, s_record.fault_address);
|
||||
}
|
||||
ESP_LOGE(TAG, " PC: 0x%08" PRIX32 " (fault location)", s_record.pc);
|
||||
ESP_LOGE(TAG, " LR: 0x%08" PRIX32 " (return address)", s_record.lr);
|
||||
ESP_LOGE(TAG, " SP: 0x%08" PRIX32, s_record.sp);
|
||||
@@ -135,61 +129,15 @@ void crash_handler_log() {
|
||||
ESP_LOGE(TAG, "%s", hint);
|
||||
}
|
||||
|
||||
// --- Crash time: exception mode, a few dozen bytes of stack: no printing, no large locals.
|
||||
// --- Crash time: exception mode, interrupts masked, a few dozen bytes of stack: no printing, no
|
||||
// large locals.
|
||||
|
||||
static inline bool is_code_addr(uint32_t val) {
|
||||
uint32_t addr = val & ~1u; // clear the Thumb bit of a return address
|
||||
#ifdef USE_BK72XX
|
||||
return (addr >= (uint32_t) _vector_start && addr < (uint32_t) _data_flash_begin) ||
|
||||
(addr >= (uint32_t) _itcmcode_ram_begin && addr < (uint32_t) _itcmcode_ram_end);
|
||||
#else
|
||||
return (addr >= (uint32_t) __xip_code_text_start__ && addr < (uint32_t) __xip_code_text_end__) ||
|
||||
(addr >= (uint32_t) __ram_entry_text_start__ && addr < (uint32_t) __ram_code_text_end__);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return addresses above sp give the backtrace; then the magic marks the record complete.
|
||||
static void finish_record(uint32_t sp, uint32_t sram_start, uint32_t sram_end) {
|
||||
uint32_t count = 0;
|
||||
if (sp >= sram_start && sp < sram_end && (sp & 3) == 0) {
|
||||
auto *scan = (const uint32_t *) sp; // NOLINT(performance-no-int-to-ptr)
|
||||
auto *end = (const uint32_t *) sram_end; // NOLINT(performance-no-int-to-ptr)
|
||||
for (size_t i = 0; i < STACK_SCAN_WORDS && scan < end && count < MAX_BACKTRACE; i++, scan++) {
|
||||
uint32_t val = *scan;
|
||||
if (is_code_addr(val) && val != s_record.lr && (val & ~1u) != s_record.pc)
|
||||
s_record.backtrace[count++] = val;
|
||||
}
|
||||
}
|
||||
if (count < MAX_BACKTRACE)
|
||||
s_record.backtrace[count] = 0; // terminator, the reader stops at the first 0
|
||||
s_record.magic = CRASH_MAGIC_V1;
|
||||
}
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
|
||||
// With Wi-Fi up the boot-time watchdog no longer fires: any interrupts-off spin hangs until power is
|
||||
// cycled (BK7238 and BK7231N; cause not in the open SDK). Re-armed here, long enough for the SDK's
|
||||
// register dump when sdk_silent is off.
|
||||
static constexpr uint32_t CRASH_REBOOT_WDT_MS = 500;
|
||||
|
||||
// SRAM: 192 KB on the BK7231N, 256 KB on the BK7238.
|
||||
static constexpr uint32_t SRAM_START = 0x00400000;
|
||||
#ifdef USE_LIBRETINY_VARIANT_BK7231N
|
||||
static constexpr uint32_t SRAM_END = 0x00430000;
|
||||
#else
|
||||
static constexpr uint32_t SRAM_END = 0x00440000;
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t CPSR_MODE_MASK = 0x1F;
|
||||
static constexpr uint32_t CPSR_MODE_USR = 0x10;
|
||||
static constexpr uint32_t CPSR_MODE_FIQ = 0x11;
|
||||
static constexpr uint32_t CPSR_MODE_IRQ = 0x12;
|
||||
static constexpr uint32_t CPSR_MODE_SVC = 0x13;
|
||||
static constexpr uint32_t CPSR_MODE_ABT = 0x17;
|
||||
static constexpr uint32_t CPSR_MODE_UND = 0x1B;
|
||||
static constexpr uint32_t CPSR_MODE_SYS = 0x1F;
|
||||
static constexpr uint32_t CPSR_THUMB = 1u << 5;
|
||||
|
||||
// PUSH_ALL_ARM_REG (boot_handlers.S) saves every mode's registers at the arm_mcu_pub.h addresses
|
||||
// before bk_trap_*; lr (r14) follows sp (r13).
|
||||
struct SavedBank {
|
||||
@@ -226,9 +174,21 @@ static void record_crash(arm_registers *regs, uint32_t reason, uint32_t pc_offse
|
||||
s_record.pc = regs->pc - pc_offset;
|
||||
s_record.lr = lr;
|
||||
s_record.sp = sp;
|
||||
s_record.fault_status = 0;
|
||||
s_record.fault_address = 0;
|
||||
finish_record(sp, SRAM_START, SRAM_END);
|
||||
|
||||
// Return addresses above sp give the backtrace.
|
||||
uint32_t count = 0;
|
||||
if (sp >= SRAM_START && sp < SRAM_END && (sp & 3) == 0) {
|
||||
auto *scan = (const uint32_t *) sp; // NOLINT(performance-no-int-to-ptr)
|
||||
auto *end = (const uint32_t *) SRAM_END; // NOLINT(performance-no-int-to-ptr)
|
||||
for (size_t i = 0; i < STACK_SCAN_WORDS && scan < end && count < MAX_BACKTRACE; i++, scan++) {
|
||||
uint32_t val = *scan;
|
||||
if (is_code_addr(val) && val != lr && (val & ~1u) != s_record.pc)
|
||||
s_record.backtrace[count++] = val;
|
||||
}
|
||||
}
|
||||
if (count < MAX_BACKTRACE)
|
||||
s_record.backtrace[count] = 0; // terminator, the reader stops at the first 0
|
||||
s_record.magic = CRASH_MAGIC_V1;
|
||||
|
||||
lt_wdt_enable(CRASH_REBOOT_WDT_MS);
|
||||
}
|
||||
@@ -251,105 +211,7 @@ extern "C" void __wrap_bk_trap_dabt(arm_registers *regs) {
|
||||
}
|
||||
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||
|
||||
#endif // USE_BK72XX
|
||||
|
||||
#ifdef USE_RTL87XX
|
||||
|
||||
// The SDK points the fault vectors (3 HardFault to 7 SecureFault) of its RAM vector table (VTOR) at a
|
||||
// ROM handler that prints a register dump and then spins. crash_handler_install() puts
|
||||
// esphome_fault_entry() in front of it, as the SDK's own CONFIG_MIIO patch (app_start.c) does.
|
||||
static_assert(LT_HW_WATCHDOG, "the crash reboot relies on the hardware watchdog");
|
||||
// The ROM dump takes about 0.4 s at 115200 baud; without the re-arm the reset waits for the 10 s
|
||||
// watchdog from arch_init().
|
||||
static constexpr uint32_t CRASH_REBOOT_WDT_MS = 1000;
|
||||
static constexpr uint32_t FIRST_FAULT_VECTOR = 3;
|
||||
static constexpr uint32_t FAULT_VECTOR_COUNT = 5;
|
||||
|
||||
// Architectural System Control Block registers (ARMv8-M).
|
||||
static constexpr uintptr_t SCB_VTOR = 0xE000ED08;
|
||||
static constexpr uintptr_t SCB_CFSR = 0xE000ED28;
|
||||
static constexpr uintptr_t SCB_MMFAR = 0xE000ED34;
|
||||
static constexpr uintptr_t SCB_BFAR = 0xE000ED38;
|
||||
static constexpr uint32_t CFSR_MMARVALID = 1u << 7;
|
||||
static constexpr uint32_t CFSR_BFARVALID = 1u << 15;
|
||||
|
||||
// DTCM, where the app's stacks and heap live (rtl8710c_ram.ld).
|
||||
static constexpr uint32_t SRAM_START = 0x10000000;
|
||||
static constexpr uint32_t SRAM_END = 0x1003FA00;
|
||||
|
||||
// Exception frame: r0-r3, r12, lr, pc, xPSR (the RTL8720C has no FPU, so never the extended frame).
|
||||
static constexpr uint32_t FRAME_LR = 5;
|
||||
static constexpr uint32_t FRAME_PC = 6;
|
||||
static constexpr uint32_t FRAME_XPSR = 7;
|
||||
static constexpr uint32_t FRAME_WORDS = 8;
|
||||
static constexpr uint32_t XPSR_STACK_ALIGNED = 1u << 9;
|
||||
|
||||
// The ROM handlers fault_entry() continues into, by vector.
|
||||
static uint32_t s_rom_fault_vectors[FAULT_VECTOR_COUNT]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static inline uint32_t read_reg(uintptr_t addr) {
|
||||
return *(const volatile uint32_t *) addr; // NOLINT(performance-no-int-to-ptr)
|
||||
}
|
||||
|
||||
// Returns the ROM handler to continue into. pc, lr and sp stay 0 when the frame is outside DTCM.
|
||||
extern "C" __attribute__((used)) uint32_t esphome_record_fault(const uint32_t *frame, uint32_t vector) {
|
||||
uint32_t cfsr = read_reg(SCB_CFSR);
|
||||
uint32_t address = 0;
|
||||
if (cfsr & CFSR_BFARVALID) {
|
||||
address = read_reg(SCB_BFAR);
|
||||
} else if (cfsr & CFSR_MMARVALID) {
|
||||
address = read_reg(SCB_MMFAR);
|
||||
}
|
||||
s_record.reason = vector + CRASH_REASON_CORTEX_M_OFFSET;
|
||||
s_record.fault_status = cfsr;
|
||||
s_record.fault_address = address;
|
||||
|
||||
auto frame_addr = (uint32_t) frame;
|
||||
uint32_t sp = 0;
|
||||
if (frame_addr >= SRAM_START && frame_addr + FRAME_WORDS * 4 <= SRAM_END && (frame_addr & 3) == 0) {
|
||||
s_record.pc = frame[FRAME_PC];
|
||||
s_record.lr = frame[FRAME_LR];
|
||||
sp = frame_addr + FRAME_WORDS * 4 + ((frame[FRAME_XPSR] & XPSR_STACK_ALIGNED) ? 4 : 0);
|
||||
} else {
|
||||
s_record.pc = 0;
|
||||
s_record.lr = 0;
|
||||
}
|
||||
s_record.sp = sp;
|
||||
finish_record(sp, SRAM_START, SRAM_END);
|
||||
|
||||
lt_wdt_enable(CRASH_REBOOT_WDT_MS);
|
||||
return s_rom_fault_vectors[vector - FIRST_FAULT_VECTOR];
|
||||
}
|
||||
|
||||
// Passes the faulting stack's frame and the vector number to esphome_record_fault, then
|
||||
// branches to the ROM handler with r0-r3 and lr as they were on entry.
|
||||
extern "C" __attribute__((naked, used)) void esphome_fault_entry() {
|
||||
__asm volatile("push {r0-r3, r4, lr} \n"
|
||||
"tst lr, #4 \n"
|
||||
"ite eq \n"
|
||||
"mrseq r0, msp \n"
|
||||
"mrsne r0, psp \n"
|
||||
"it eq \n"
|
||||
"addeq r0, r0, #24 \n" // MSP: skip the six words pushed above
|
||||
"mrs r1, ipsr \n"
|
||||
"ubfx r1, r1, #0, #9 \n"
|
||||
"bl esphome_record_fault \n"
|
||||
"mov r12, r0 \n"
|
||||
"pop {r0-r3, r4, lr} \n"
|
||||
"bx r12 \n");
|
||||
}
|
||||
|
||||
void crash_handler_install() {
|
||||
auto *vectors = (uint32_t *) read_reg(SCB_VTOR); // NOLINT(performance-no-int-to-ptr)
|
||||
for (uint32_t i = 0; i < FAULT_VECTOR_COUNT; i++) {
|
||||
s_rom_fault_vectors[i] = vectors[FIRST_FAULT_VECTOR + i];
|
||||
vectors[FIRST_FAULT_VECTOR + i] = (uint32_t) &esphome_fault_entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_RTL87XX
|
||||
|
||||
} // namespace esphome::libretiny
|
||||
|
||||
#endif // USE_LIBRETINY_CRASH_HANDLER
|
||||
#endif // USE_LIBRETINY
|
||||
#endif // USE_BK72XX
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#ifdef USE_BK72XX
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
@@ -14,12 +14,7 @@ void crash_handler_log();
|
||||
/// Returns true if crash data was found this boot, reading it first if needed.
|
||||
bool crash_handler_has_data();
|
||||
|
||||
#ifdef USE_RTL87XX
|
||||
/// Put the crash recorder in front of the SDK's fault handlers.
|
||||
void crash_handler_install();
|
||||
#endif
|
||||
|
||||
} // namespace esphome::libretiny
|
||||
|
||||
#endif // USE_LIBRETINY_CRASH_HANDLER
|
||||
#endif // USE_LIBRETINY
|
||||
#endif // USE_BK72XX
|
||||
@@ -152,7 +152,6 @@ COMPONENT_CODEOWNERS = {
|
||||
# Extra imports per component; the CLI looks up process_stacktrace on the platform package.
|
||||
COMPONENT_EXTRA_IMPORTS = {
|
||||
"bk72xx": "from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401",
|
||||
"rtl87xx": "from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "core.h"
|
||||
#include "crash_handler.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "preferences.h"
|
||||
|
||||
@@ -28,9 +27,6 @@ namespace esphome {
|
||||
void arch_init() {
|
||||
libretiny::setup_preferences();
|
||||
lt_wdt_enable(10000L);
|
||||
#if defined(USE_LIBRETINY_CRASH_HANDLER) && defined(USE_RTL87XX)
|
||||
libretiny::crash_handler_install();
|
||||
#endif
|
||||
#ifdef USE_BK72XX
|
||||
// BK72xx SDK creates the main Arduino task at priority 3, which is lower than
|
||||
// all WiFi (4-5), LwIP (4), and TCP/IP (7) tasks. This causes ~100ms loop
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
@@ -91,10 +94,13 @@ from esphome.core.entity_helpers import (
|
||||
setup_entity,
|
||||
setup_unit_of_measurement,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.types import ConfigType
|
||||
from esphome.cpp_generator import MockObj, MockObjClass, ProgmemAssignmentExpression
|
||||
from esphome.types import ConfigType, SafeExpType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DOMAIN = "number"
|
||||
CONF_RANGE_ID = "range_id"
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
@@ -162,6 +168,7 @@ IS_PLATFORM_COMPONENT = True
|
||||
number_ns = cg.esphome_ns.namespace("number")
|
||||
Number = number_ns.class_("Number", cg.EntityBase)
|
||||
NumberPtr = Number.operator("ptr")
|
||||
NumberRange = number_ns.struct("NumberRange")
|
||||
ApplyNumber = number_ns.class_("ApplyNumber", Number)
|
||||
|
||||
# Triggers
|
||||
@@ -199,6 +206,7 @@ _NUMBER_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTNumberComponent")
|
||||
),
|
||||
cv.GenerateID(CONF_RANGE_ID): cv.declare_id(NumberRange),
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
|
||||
{
|
||||
@@ -274,13 +282,74 @@ async def _build_number_automations(var, config):
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
|
||||
|
||||
# A shared table only pays for set_range() and the table itself once this many numbers use it.
|
||||
RANGE_TABLE_MIN_USERS = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class NumberData:
|
||||
# (min, max, step) -> range_id of every number using it; the first one names the table.
|
||||
ranges: dict[tuple[float, float, float], list[ID]] = field(default_factory=dict)
|
||||
|
||||
|
||||
def _get_data() -> NumberData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = NumberData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _RenderTimeStatement(cg.Statement):
|
||||
"""Text chosen when main.cpp is written, after every number has been counted."""
|
||||
|
||||
render: Callable[[], str]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.render()
|
||||
|
||||
|
||||
def _add_range(
|
||||
var: MockObj,
|
||||
range_id: ID,
|
||||
min_value: SafeExpType,
|
||||
max_value: SafeExpType,
|
||||
step: SafeExpType,
|
||||
) -> None:
|
||||
setters = (
|
||||
var.traits.set_min_value(min_value),
|
||||
var.traits.set_max_value(max_value),
|
||||
var.traits.set_step(step),
|
||||
)
|
||||
# Lambda bounds (e.g. lvgl arc/bar) are runtime expressions and can't go in a table.
|
||||
if not all(isinstance(v, (int, float)) for v in (min_value, max_value, step)):
|
||||
for expr in setters:
|
||||
cg.add(expr)
|
||||
return
|
||||
key = (float(min_value), float(max_value), float(step))
|
||||
users = _get_data().ranges.setdefault(key, [])
|
||||
users.append(range_id)
|
||||
table = users[0]
|
||||
|
||||
def render_table() -> str:
|
||||
if len(users) < RANGE_TABLE_MIN_USERS:
|
||||
return ""
|
||||
return f"{ProgmemAssignmentExpression(NumberRange, table, cg.safe_exp([key]))};"
|
||||
|
||||
def render_call() -> str:
|
||||
if len(users) < RANGE_TABLE_MIN_USERS:
|
||||
return "\n".join(str(cg.statement(expr)) for expr in setters)
|
||||
return str(cg.statement(var.set_range(MockObj(table, "."))))
|
||||
|
||||
if len(users) == 1:
|
||||
cg.add_global(_RenderTimeStatement(render_table))
|
||||
cg.add(_RenderTimeStatement(render_call))
|
||||
|
||||
|
||||
@setup_entity("number")
|
||||
async def setup_number_core_(
|
||||
var, config, *, min_value: float, max_value: float, step: float
|
||||
):
|
||||
cg.add(var.traits.set_min_value(min_value))
|
||||
cg.add(var.traits.set_max_value(max_value))
|
||||
cg.add(var.traits.set_step(step))
|
||||
_add_range(var, config[CONF_RANGE_ID], min_value, max_value, step)
|
||||
|
||||
# Skip the setter when the config matches the C++ initializer (DEFAULT_MODE).
|
||||
# The validated value is the enum key string, not the C++ enum expression.
|
||||
|
||||
@@ -17,6 +17,13 @@ void log_number(const char *tag, const char *prefix, const char *type, Number *o
|
||||
LOG_ENTITY_DEVICE_CLASS(tag, prefix, *obj);
|
||||
}
|
||||
|
||||
void Number::set_range(const NumberRange *range) {
|
||||
// Aligned 32-bit loads are safe from PROGMEM on ESP8266 (same as pgm_read_float_aligned).
|
||||
this->traits.set_min_value(range->min_value);
|
||||
this->traits.set_max_value(range->max_value);
|
||||
this->traits.set_step(range->step);
|
||||
}
|
||||
|
||||
void Number::publish_state(float state) {
|
||||
this->set_has_state(true);
|
||||
this->state = state;
|
||||
|
||||
@@ -40,6 +40,9 @@ class Number : public EntityBase {
|
||||
|
||||
NumberTraits traits;
|
||||
|
||||
// Out of line so each generated call site loads one pointer instead of three floats.
|
||||
void set_range(const NumberRange *range);
|
||||
|
||||
protected:
|
||||
friend class NumberCall;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
@@ -11,6 +12,18 @@ enum NumberMode : uint8_t {
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
|
||||
/// Boundaries and step for a number; codegen shares one PROGMEM table between numbers with the same values.
|
||||
struct NumberRange {
|
||||
float min_value;
|
||||
float max_value;
|
||||
float step;
|
||||
};
|
||||
// Read straight from flash on ESP8266, so every field must stay a word.
|
||||
static_assert(std::is_same_v<decltype(NumberRange::min_value), float>, "NumberRange fields must stay float");
|
||||
static_assert(std::is_same_v<decltype(NumberRange::max_value), float>, "NumberRange fields must stay float");
|
||||
static_assert(std::is_same_v<decltype(NumberRange::step), float>, "NumberRange fields must stay float");
|
||||
static_assert(sizeof(NumberRange) == 3 * sizeof(float), "NumberRange is read from flash with word loads");
|
||||
|
||||
class NumberTraits {
|
||||
public:
|
||||
// Set/get the number value boundaries.
|
||||
|
||||
@@ -29,7 +29,6 @@ from esphome.components.libretiny.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.stacktrace import process_crash_handler_line as process_stacktrace # noqa: F401
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS
|
||||
|
||||
@@ -774,7 +774,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#if defined(USE_BK72XX) || defined(USE_RTL87XX)
|
||||
#ifdef USE_BK72XX
|
||||
#define USE_LIBRETINY_CRASH_HANDLER
|
||||
#endif
|
||||
#define USE_BK72XX_BLE
|
||||
|
||||
@@ -28,7 +28,6 @@ from esphome.const import (
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
Platform,
|
||||
)
|
||||
|
||||
@@ -65,7 +64,6 @@ STACKTRACE_GATES: Final[dict[str, str]] = {
|
||||
),
|
||||
PLATFORM_RP2: _CRASH_HANDLER_GATE,
|
||||
PLATFORM_BK72XX: _CRASH_HANDLER_GATE,
|
||||
PLATFORM_RTL87XX: _CRASH_HANDLER_GATE,
|
||||
PLATFORM_NRF52: r"0x[0-9a-fA-F]{3,}\b|Last crash:",
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ def _addr2line(tool: str, elf: Path, addr: str) -> str:
|
||||
def process_crash_handler_line(
|
||||
config: ConfigType, line: str, backtrace_state: bool
|
||||
) -> bool:
|
||||
"""Decode a crash handler report with addr2line; the process_stacktrace hook of rp2, bk72xx and rtl87xx."""
|
||||
"""Decode a crash handler report with addr2line; the process_stacktrace hook of rp2 and bk72xx."""
|
||||
if CRASH_HANDLER_RE.search(line):
|
||||
_LOGGER.error("Crash detected - decoding addresses")
|
||||
return True
|
||||
|
||||
@@ -57,7 +57,6 @@ PLATFORM_BUILD_SECONDS = {
|
||||
"esp32-idf": 55,
|
||||
"esp32-p4-idf": 55,
|
||||
"ln882x-ard": 55,
|
||||
"rtl8720c-ard": 55,
|
||||
"esp32-s3-idf": 60,
|
||||
"bk72xx-ard": 70,
|
||||
"bk7238-ard": 70,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
rtl87xx:
|
||||
board: generic-rtl8710bn-2mb-788k
|
||||
@@ -1,5 +0,0 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
rtl87xx:
|
||||
board: bw15
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The crash handler is built for BK7238, BK7231N (both with an ITCM linker region) and RTL8720C."""
|
||||
"""Only BK7238 and BK7231N, the BK chips with an ITCM linker region, get the crash handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -12,14 +12,12 @@ from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "enabled", "bk_hooks"),
|
||||
("config", "enabled"),
|
||||
[
|
||||
("bk7238.yaml", True, True),
|
||||
("bk72xx.yaml", True, True), # BK7231N
|
||||
("bk7231t.yaml", False, False),
|
||||
("rtl8720c.yaml", True, False), # installed at run time
|
||||
("rtl8710b.yaml", False, False),
|
||||
("ln882x.yaml", False, False),
|
||||
("bk7238.yaml", True),
|
||||
("bk72xx.yaml", True), # BK7231N
|
||||
("bk7231t.yaml", False),
|
||||
("ln882x.yaml", False),
|
||||
],
|
||||
)
|
||||
def test_crash_handler_family_gate(
|
||||
@@ -27,14 +25,13 @@ def test_crash_handler_family_gate(
|
||||
component_config_path: Callable[[str], Path],
|
||||
config: str,
|
||||
enabled: bool,
|
||||
bk_hooks: bool,
|
||||
) -> None:
|
||||
generate_main(component_config_path(config))
|
||||
defines = {define.name for define in CORE.defines}
|
||||
assert ("USE_LIBRETINY_CRASH_HANDLER" in defines) is enabled
|
||||
assert ("USE_CRASH_HANDLER" in defines) is enabled
|
||||
for trap in BK_TRAP_HANDLERS:
|
||||
assert (f"-Wl,--wrap={trap}" in CORE.build_flags) is bk_hooks
|
||||
assert (f"-Wl,--wrap={trap}" in CORE.build_flags) is enabled
|
||||
assert (
|
||||
"pre:patch_noinit.py" in CORE.platformio_options["extra_scripts"]
|
||||
) is bk_hooks
|
||||
) is enabled
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
id: shared_1
|
||||
min_value: 0
|
||||
max_value: 10
|
||||
step: 1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: shared_2
|
||||
min_value: 0
|
||||
max_value: 10
|
||||
step: 1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: shared_3
|
||||
min_value: 0
|
||||
max_value: 10
|
||||
step: 1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: single
|
||||
min_value: -5
|
||||
max_value: 5
|
||||
step: 0.5
|
||||
optimistic: true
|
||||
@@ -3,6 +3,10 @@
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.components import number
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, RawExpression
|
||||
|
||||
|
||||
def test_default_mode_is_not_emitted(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
@@ -14,3 +18,36 @@ def test_default_mode_is_not_emitted(
|
||||
assert "auto_number->traits.set_mode(" not in main_cpp
|
||||
assert "explicit_auto_number->traits.set_mode(" not in main_cpp
|
||||
assert "box_number->traits.set_mode(number::NUMBER_MODE_BOX);" in main_cpp
|
||||
|
||||
|
||||
def test_range_table_only_for_shared_ranges(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""A range used by enough numbers gets one PROGMEM table; others keep the setters."""
|
||||
main_cpp = generate_main(component_config_path("range.yaml"))
|
||||
|
||||
assert (
|
||||
"static constexpr number::NumberRange number_numberrange_id[] PROGMEM = "
|
||||
"{{0.0f, 10.0f, 1.0f}};" in main_cpp
|
||||
)
|
||||
assert main_cpp.count("number::NumberRange ") == 1
|
||||
for var in ("shared_1", "shared_2", "shared_3"):
|
||||
assert f"{var}->set_range(number_numberrange_id);" in main_cpp
|
||||
assert "single->set_range(" not in main_cpp
|
||||
assert "single->traits.set_min_value(-5.0f);" in main_cpp
|
||||
assert "single->traits.set_max_value(5.0f);" in main_cpp
|
||||
assert "single->traits.set_step(0.5f);" in main_cpp
|
||||
|
||||
|
||||
def test_range_with_lambda_bounds_uses_setters() -> None:
|
||||
"""Runtime bounds (lvgl arc/bar lambdas) can't go in a PROGMEM table."""
|
||||
var = MockObj("lambda_number", "->")
|
||||
range_id = ID("lambda_range", is_declaration=True, type=number.NumberRange)
|
||||
number._add_range(var, range_id, RawExpression("get_min()"), 10, 1)
|
||||
|
||||
main_cpp = CORE.cpp_main_section
|
||||
assert "lambda_number->traits.set_min_value(get_min());" in main_cpp
|
||||
assert "lambda_number->traits.set_max_value(10);" in main_cpp
|
||||
assert "lambda_number->traits.set_step(1);" in main_cpp
|
||||
assert "set_range(" not in main_cpp
|
||||
@@ -1,13 +0,0 @@
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
int x = 100;
|
||||
x = clamp(x, 50, 90);
|
||||
assert(x == 90);
|
||||
x = clamp_at_least(x, 95);
|
||||
assert(x == 95);
|
||||
x = clamp_at_most(x, 40);
|
||||
assert(x == 40);
|
||||
@@ -1,15 +0,0 @@
|
||||
esphome:
|
||||
name: componenttestesprtl8720c
|
||||
friendly_name: $component_name
|
||||
|
||||
rtl87xx:
|
||||
board: bw15
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
packages:
|
||||
component_under_test: !include
|
||||
file: $component_test_file
|
||||
vars:
|
||||
component_test_file: $component_test_file
|
||||
@@ -117,9 +117,9 @@ from esphome.const import (
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_HOST,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
ErrorFormat,
|
||||
Toolchain,
|
||||
)
|
||||
@@ -6192,7 +6192,7 @@ def test_run_miniterm_no_stacktrace_analyzer(
|
||||
"""Platforms without a stacktrace analyzer log an info and stream anyway."""
|
||||
mock_serial = MockSerial([b"[I][app:100]: Line 1\r\n", MOCK_SERIAL_END])
|
||||
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_LN882X}
|
||||
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_RTL87XX}
|
||||
config = {
|
||||
CONF_LOGGER: {
|
||||
CONF_BAUD_RATE: 115200,
|
||||
|
||||
@@ -15,7 +15,7 @@ from unittest.mock import Mock
|
||||
import pytest
|
||||
|
||||
from esphome import platform_hooks
|
||||
from esphome.const import PLATFORM_ESP32, PLATFORM_LN882X, Platform
|
||||
from esphome.const import PLATFORM_ESP32, PLATFORM_RTL87XX, Platform
|
||||
|
||||
|
||||
def test_no_unregistered_platform_exposes_a_hook() -> None:
|
||||
@@ -165,7 +165,7 @@ def test_get_stacktrace_handler_reports_missing_analyzer(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
caplog.set_level("INFO", logger="esphome.platform_hooks")
|
||||
assert platform_hooks.get_stacktrace_handler(PLATFORM_LN882X) is None
|
||||
assert platform_hooks.get_stacktrace_handler(PLATFORM_RTL87XX) is None
|
||||
assert "no compatible analyzer" in caplog.text
|
||||
# A capability gap is ordinary; it must not warn.
|
||||
assert not any(r.levelno >= logging.WARNING for r in caplog.records)
|
||||
|
||||
@@ -18,7 +18,6 @@ from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_NRF52,
|
||||
PLATFORM_RP2,
|
||||
PLATFORM_RTL87XX,
|
||||
@@ -67,10 +66,6 @@ CRASH_SAMPLES: dict[str, dict[str, list[str]]] = {
|
||||
"state_markers": ["CRASH DETECTED ON PREVIOUS BOOT"],
|
||||
"addresses": ["PC: 0x0001614A (fault location)"],
|
||||
},
|
||||
PLATFORM_RTL87XX: {
|
||||
"state_markers": ["CRASH DETECTED ON PREVIOUS BOOT"],
|
||||
"addresses": ["PC: 0x9B0073A4 (fault location)"],
|
||||
},
|
||||
PLATFORM_NRF52: {
|
||||
"state_markers": ["Last crash:"],
|
||||
"addresses": [
|
||||
@@ -236,7 +231,6 @@ DECODER_PATTERNS: dict[str, list[str]] = {
|
||||
],
|
||||
PLATFORM_RP2: ["CRASH_HANDLER_RE", "CRASH_HANDLER_ADDR_RE"],
|
||||
PLATFORM_BK72XX: ["CRASH_HANDLER_RE", "CRASH_HANDLER_ADDR_RE"],
|
||||
PLATFORM_RTL87XX: ["CRASH_HANDLER_RE", "CRASH_HANDLER_ADDR_RE"],
|
||||
PLATFORM_NRF52: ["STACKTRACE_NRF52_PC_LR_RE"],
|
||||
}
|
||||
|
||||
@@ -554,7 +548,7 @@ def test_processor_registry_miss_disables_at_construction(
|
||||
caplog.set_level("INFO", logger="esphome.platform_hooks")
|
||||
|
||||
with patch.object(stacktrace.platform_hooks, "import_module") as mock_import:
|
||||
processor = stacktrace.LogLineProcessor(CONFIG, PLATFORM_LN882X)
|
||||
processor = stacktrace.LogLineProcessor(CONFIG, PLATFORM_RTL87XX)
|
||||
processor.process_line("PC: 0x40104960")
|
||||
|
||||
mock_import.assert_not_called()
|
||||
|
||||
Reference in new issue
Block a user