mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 18:16:03 +00:00
Merge branch 'crash-handler-dual-core' into integration
This commit is contained in:
@@ -79,6 +79,7 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
float_,
|
||||
global_ns,
|
||||
gpio_Flags,
|
||||
int8,
|
||||
int16,
|
||||
int32,
|
||||
int64,
|
||||
|
||||
@@ -97,7 +97,7 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
|
||||
async def ags10newi2caddress_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
address = await cg.templatable(config[CONF_ADDRESS], args, int)
|
||||
address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_new_address(address))
|
||||
return var
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config.get(CONF_MODE), args, int)
|
||||
template_ = await cg.templatable(config.get(CONF_MODE), args, cg.uint8)
|
||||
cg.add(var.set_auto_mute_mode(template_))
|
||||
|
||||
return var
|
||||
|
||||
@@ -169,14 +169,13 @@ async def at581x_settings_to_code(config, action_id, template_arg, args):
|
||||
|
||||
# Radar configuration
|
||||
if frontend_reset := config.get(CONF_HW_FRONTEND_RESET):
|
||||
template_ = await cg.templatable(frontend_reset, args, int)
|
||||
template_ = await cg.templatable(frontend_reset, args, cg.int8)
|
||||
cg.add(var.set_hw_frontend_reset(template_))
|
||||
|
||||
if freq := config.get(CONF_FREQUENCY):
|
||||
if cg.is_template(freq):
|
||||
template_ = await cg.templatable(freq, args, cg.int32)
|
||||
else:
|
||||
template_ = int(freq / 1000000)
|
||||
if not cg.is_template(freq):
|
||||
freq = int(freq / 1000000)
|
||||
template_ = await cg.templatable(freq, args, cg.int_)
|
||||
cg.add(var.set_frequency(template_))
|
||||
|
||||
if (sens_dist := config.get(CONF_SENSING_DISTANCE)) is not None:
|
||||
@@ -204,10 +203,9 @@ async def at581x_settings_to_code(config, action_id, template_arg, args):
|
||||
cg.add(var.set_stage_gain(template_))
|
||||
|
||||
if power := config.get(CONF_POWER_CONSUMPTION):
|
||||
if cg.is_template(power):
|
||||
template_ = await cg.templatable(power, args, cg.int32)
|
||||
else:
|
||||
template_ = int(power * 1000000)
|
||||
if not cg.is_template(power):
|
||||
power = int(power * 1000000)
|
||||
template_ = await cg.templatable(power, args, cg.int_)
|
||||
cg.add(var.set_power_consumption(template_))
|
||||
|
||||
return var
|
||||
|
||||
@@ -159,7 +159,7 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
template_ = await cg.templatable(factory_reset_config, args, int)
|
||||
template_ = await cg.templatable(factory_reset_config, args, cg.int8)
|
||||
cg.add(var.set_factory_reset(template_))
|
||||
|
||||
if CONF_DETECTION_SEGMENTS in config:
|
||||
@@ -200,7 +200,7 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args
|
||||
template_ = template_.total_milliseconds / 1000
|
||||
cg.add(var.set_delay_after_disappear(template_))
|
||||
if CONF_SENSITIVITY in config:
|
||||
template_ = await cg.templatable(config[CONF_SENSITIVITY], args, int)
|
||||
template_ = await cg.templatable(config[CONF_SENSITIVITY], args, cg.int8)
|
||||
cg.add(var.set_sensitivity(template_))
|
||||
|
||||
return var
|
||||
|
||||
@@ -59,6 +59,59 @@ static inline bool is_return_addr(uint32_t addr) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- Architecture-specific backtrace helpers ---
|
||||
// These run from IRAM during panic (no flash access).
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
// Walk Xtensa backtrace from an exception frame, writing PCs to out[].
|
||||
// Returns number of entries written.
|
||||
static uint8_t IRAM_ATTR walk_xtensa_backtrace(XtExcFrame *frame, uint32_t *out, uint8_t max) {
|
||||
esp_backtrace_frame_t bt_frame = {
|
||||
.pc = (uint32_t) frame->pc,
|
||||
.sp = (uint32_t) frame->a1,
|
||||
.next_pc = (uint32_t) frame->a0,
|
||||
.exc_frame = frame,
|
||||
};
|
||||
uint8_t count = 0;
|
||||
uint32_t first_pc = esp_cpu_process_stack_pc(bt_frame.pc);
|
||||
if (is_code_addr(first_pc)) {
|
||||
out[count++] = first_pc;
|
||||
}
|
||||
while (count < max && bt_frame.next_pc != 0) {
|
||||
if (!esp_backtrace_get_next_frame(&bt_frame))
|
||||
break;
|
||||
uint32_t pc = esp_cpu_process_stack_pc(bt_frame.pc);
|
||||
if (is_code_addr(pc)) {
|
||||
out[count++] = pc;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// Capture RISC-V backtrace: MEPC + RA from registers, then stack scan.
|
||||
// Returns total count; *reg_count receives number of register-sourced entries.
|
||||
static uint8_t IRAM_ATTR capture_riscv_backtrace(RvExcFrame *frame, uint32_t *out, uint8_t max, uint8_t *reg_count) {
|
||||
uint8_t count = 0;
|
||||
if (is_code_addr(frame->mepc)) {
|
||||
out[count++] = frame->mepc;
|
||||
}
|
||||
if (is_code_addr(frame->ra) && frame->ra != frame->mepc) {
|
||||
out[count++] = frame->ra;
|
||||
}
|
||||
*reg_count = count;
|
||||
auto *scan_start = (uint32_t *) frame->sp;
|
||||
for (uint32_t i = 0; i < 64 && count < max; i++) {
|
||||
uint32_t val = scan_start[i];
|
||||
if (is_code_addr(val) && val != frame->mepc && val != frame->ra) {
|
||||
out[count++] = val;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Raw crash data written by the panic handler wrapper.
|
||||
// Lives in .noinit so it survives software reset but contains garbage after power cycle.
|
||||
// Validated by magic marker. Static linkage since it's only used within this file.
|
||||
@@ -66,7 +119,7 @@ static inline bool is_return_addr(uint32_t addr) {
|
||||
// Magic is second to validate the data. Remaining fields can change between versions.
|
||||
// Version is uint32_t because it would be padded to 4 bytes anyway before the next
|
||||
// uint32_t field, so we use the full width rather than wasting 3 bytes of padding.
|
||||
static constexpr uint32_t CRASH_DATA_VERSION = 1;
|
||||
static constexpr uint32_t CRASH_DATA_VERSION = 2;
|
||||
struct RawCrashData {
|
||||
uint32_t version;
|
||||
uint32_t magic;
|
||||
@@ -77,6 +130,13 @@ struct RawCrashData {
|
||||
uint8_t pseudo_excause; // Whether cause is a pseudo exception (Xtensa SoC-level panic)
|
||||
uint32_t backtrace[MAX_BACKTRACE];
|
||||
uint32_t cause; // Architecture-specific: exccause (Xtensa) or mcause (RISC-V)
|
||||
uint8_t crashed_core;
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
static_assert(SOC_CPU_CORES_NUM == 2, "Dual-core logic assumes exactly 2 cores");
|
||||
uint8_t other_backtrace_count;
|
||||
uint8_t other_reg_frame_count;
|
||||
uint32_t other_backtrace[MAX_BACKTRACE];
|
||||
#endif
|
||||
};
|
||||
static RawCrashData __attribute__((section(".noinit")))
|
||||
s_raw_crash_data; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -100,6 +160,14 @@ void crash_handler_read_and_clear() {
|
||||
s_raw_crash_data.exception = 4; // Default to PANIC_EXCEPTION_FAULT
|
||||
if (s_raw_crash_data.pseudo_excause > 1)
|
||||
s_raw_crash_data.pseudo_excause = 0;
|
||||
if (s_raw_crash_data.crashed_core >= SOC_CPU_CORES_NUM)
|
||||
s_raw_crash_data.crashed_core = 0;
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
if (s_raw_crash_data.other_backtrace_count > MAX_BACKTRACE)
|
||||
s_raw_crash_data.other_backtrace_count = MAX_BACKTRACE;
|
||||
if (s_raw_crash_data.other_reg_frame_count > s_raw_crash_data.other_backtrace_count)
|
||||
s_raw_crash_data.other_reg_frame_count = s_raw_crash_data.other_backtrace_count;
|
||||
#endif
|
||||
}
|
||||
// Clear magic regardless so we don't re-report on next normal reboot
|
||||
s_raw_crash_data.magic = 0;
|
||||
@@ -212,6 +280,36 @@ static const char *get_exception_type() {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// Log backtrace entries, filtering stack-scanned addresses on RISC-V.
|
||||
static void log_backtrace(const uint32_t *addrs, uint8_t count, uint8_t reg_frame_count) {
|
||||
uint8_t bt_num = 0;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
uint32_t addr = addrs[i];
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
if (i >= reg_frame_count && !is_return_addr(addr))
|
||||
continue;
|
||||
const char *source = (i < reg_frame_count) ? "backtrace" : "stack scan";
|
||||
#else
|
||||
const char *source = "backtrace";
|
||||
#endif
|
||||
ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32 " (%s)", bt_num++, addr, source);
|
||||
}
|
||||
}
|
||||
|
||||
// Append backtrace addresses to the addr2line hint buffer.
|
||||
static int append_addrs_to_hint(char *buf, int size, int pos, const uint32_t *addrs, uint8_t count,
|
||||
uint8_t reg_frame_count) {
|
||||
for (uint8_t i = 0; i < count && pos < size - 12; i++) {
|
||||
uint32_t addr = addrs[i];
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
if (i >= reg_frame_count && !is_return_addr(addr))
|
||||
continue;
|
||||
#endif
|
||||
pos += snprintf(buf + pos, size - pos, " 0x%08" PRIX32, addr);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Intentionally uses separate ESP_LOGE calls per line instead of combining into
|
||||
// one multi-line log message. This ensures each address appears as its own line
|
||||
// on the serial console, making it possible to see partial output if the device
|
||||
@@ -228,33 +326,28 @@ void crash_handler_log() {
|
||||
} else {
|
||||
ESP_LOGE(TAG, " Reason: %s", get_exception_type());
|
||||
}
|
||||
ESP_LOGE(TAG, " Crashed core: %d", s_raw_crash_data.crashed_core);
|
||||
ESP_LOGE(TAG, " PC: 0x%08" PRIX32 " (fault location)", s_raw_crash_data.pc);
|
||||
uint8_t bt_num = 0;
|
||||
for (uint8_t i = 0; i < s_raw_crash_data.backtrace_count; i++) {
|
||||
uint32_t addr = s_raw_crash_data.backtrace[i];
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// Register-sourced entries (MEPC/RA) are trusted; only filter stack-scanned ones.
|
||||
if (i >= s_raw_crash_data.reg_frame_count && !is_return_addr(addr))
|
||||
continue;
|
||||
#endif
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
const char *source = (i < s_raw_crash_data.reg_frame_count) ? "backtrace" : "stack scan";
|
||||
#else
|
||||
const char *source = "backtrace";
|
||||
#endif
|
||||
ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32 " (%s)", bt_num++, addr, source);
|
||||
log_backtrace(s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count, s_raw_crash_data.reg_frame_count);
|
||||
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
if (s_raw_crash_data.other_backtrace_count > 0) {
|
||||
int other_core = 1 - s_raw_crash_data.crashed_core;
|
||||
ESP_LOGE(TAG, " Other core (%d) backtrace:", other_core);
|
||||
log_backtrace(s_raw_crash_data.other_backtrace, s_raw_crash_data.other_backtrace_count,
|
||||
s_raw_crash_data.other_reg_frame_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Build addr2line hint with all captured addresses for easy copy-paste
|
||||
char hint[256];
|
||||
int pos = snprintf(hint, sizeof(hint), "Use: addr2line -pfiaC -e firmware.elf 0x%08" PRIX32, s_raw_crash_data.pc);
|
||||
for (uint8_t i = 0; i < s_raw_crash_data.backtrace_count && pos < (int) sizeof(hint) - 12; i++) {
|
||||
uint32_t addr = s_raw_crash_data.backtrace[i];
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
if (i >= s_raw_crash_data.reg_frame_count && !is_return_addr(addr))
|
||||
continue;
|
||||
pos = append_addrs_to_hint(hint, sizeof(hint), pos, s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count,
|
||||
s_raw_crash_data.reg_frame_count);
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
append_addrs_to_hint(hint, sizeof(hint), pos, s_raw_crash_data.other_backtrace,
|
||||
s_raw_crash_data.other_backtrace_count, s_raw_crash_data.other_reg_frame_count);
|
||||
#endif
|
||||
pos += snprintf(hint + pos, sizeof(hint) - pos, " 0x%08" PRIX32, addr);
|
||||
}
|
||||
ESP_LOGE(TAG, "%s", hint);
|
||||
}
|
||||
|
||||
@@ -276,68 +369,54 @@ void IRAM_ATTR __wrap_esp_panic_handler(panic_info_t *info) {
|
||||
s_raw_crash_data.reg_frame_count = 0;
|
||||
s_raw_crash_data.exception = (uint8_t) info->exception;
|
||||
s_raw_crash_data.pseudo_excause = info->pseudo_excause ? 1 : 0;
|
||||
s_raw_crash_data.crashed_core = (uint8_t) info->core;
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
s_raw_crash_data.other_backtrace_count = 0;
|
||||
s_raw_crash_data.other_reg_frame_count = 0;
|
||||
#endif
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
// Xtensa: walk the backtrace using the public API
|
||||
if (info->frame != nullptr) {
|
||||
auto *xt_frame = (XtExcFrame *) info->frame;
|
||||
s_raw_crash_data.cause = xt_frame->exccause;
|
||||
esp_backtrace_frame_t bt_frame = {
|
||||
.pc = (uint32_t) xt_frame->pc,
|
||||
.sp = (uint32_t) xt_frame->a1,
|
||||
.next_pc = (uint32_t) xt_frame->a0,
|
||||
.exc_frame = xt_frame,
|
||||
};
|
||||
|
||||
uint8_t count = 0;
|
||||
// First frame PC
|
||||
uint32_t first_pc = esp_cpu_process_stack_pc(bt_frame.pc);
|
||||
if (is_code_addr(first_pc)) {
|
||||
s_raw_crash_data.backtrace[count++] = first_pc;
|
||||
}
|
||||
// Walk remaining frames
|
||||
while (count < MAX_BACKTRACE && bt_frame.next_pc != 0) {
|
||||
if (!esp_backtrace_get_next_frame(&bt_frame)) {
|
||||
break;
|
||||
}
|
||||
uint32_t pc = esp_cpu_process_stack_pc(bt_frame.pc);
|
||||
if (is_code_addr(pc)) {
|
||||
s_raw_crash_data.backtrace[count++] = pc;
|
||||
}
|
||||
}
|
||||
s_raw_crash_data.backtrace_count = count;
|
||||
s_raw_crash_data.backtrace_count = walk_xtensa_backtrace(xt_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE);
|
||||
}
|
||||
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
// Capture the other core's backtrace from the global frame array.
|
||||
// Both cores save their frames to g_exc_frames[] before esp_panic_handler
|
||||
// is called, so the other core's frame is available here.
|
||||
if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) {
|
||||
int other_core = 1 - info->core;
|
||||
auto *other_frame = (XtExcFrame *) g_exc_frames[other_core];
|
||||
if (other_frame != nullptr) {
|
||||
s_raw_crash_data.other_backtrace_count =
|
||||
walk_xtensa_backtrace(other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// RISC-V: capture MEPC + RA, then scan stack for code addresses
|
||||
if (info->frame != nullptr) {
|
||||
auto *rv_frame = (RvExcFrame *) info->frame;
|
||||
s_raw_crash_data.cause = rv_frame->mcause;
|
||||
uint8_t count = 0;
|
||||
|
||||
// Save MEPC (fault PC) and RA (return address)
|
||||
if (is_code_addr(rv_frame->mepc)) {
|
||||
s_raw_crash_data.backtrace[count++] = rv_frame->mepc;
|
||||
}
|
||||
if (is_code_addr(rv_frame->ra) && rv_frame->ra != rv_frame->mepc) {
|
||||
s_raw_crash_data.backtrace[count++] = rv_frame->ra;
|
||||
}
|
||||
|
||||
// Track how many entries came from registers (MEPC/RA) so we can
|
||||
// skip return-address validation for them at log time.
|
||||
s_raw_crash_data.reg_frame_count = count;
|
||||
|
||||
// Scan stack for code addresses — captures broadly during panic,
|
||||
// filtered by is_return_addr() at log time when flash is accessible.
|
||||
auto *scan_start = (uint32_t *) rv_frame->sp;
|
||||
for (uint32_t i = 0; i < 64 && count < MAX_BACKTRACE; i++) {
|
||||
uint32_t val = scan_start[i];
|
||||
if (is_code_addr(val) && val != rv_frame->mepc && val != rv_frame->ra) {
|
||||
s_raw_crash_data.backtrace[count++] = val;
|
||||
}
|
||||
}
|
||||
s_raw_crash_data.backtrace_count = count;
|
||||
s_raw_crash_data.backtrace_count =
|
||||
capture_riscv_backtrace(rv_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE, &s_raw_crash_data.reg_frame_count);
|
||||
}
|
||||
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
// Capture the other core's backtrace from the global frame array.
|
||||
if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) {
|
||||
int other_core = 1 - info->core;
|
||||
auto *other_frame = (RvExcFrame *) g_exc_frames[other_core];
|
||||
if (other_frame != nullptr) {
|
||||
s_raw_crash_data.other_backtrace_count = capture_riscv_backtrace(
|
||||
other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE, &s_raw_crash_data.other_reg_frame_count);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Write version and magic last — ensures all data is written before we mark it valid
|
||||
|
||||
@@ -78,7 +78,7 @@ async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
cg.add(var.set_speed(template_speed))
|
||||
@@ -101,7 +101,7 @@ async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
return var
|
||||
|
||||
@@ -121,7 +121,7 @@ async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, int)
|
||||
template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_channel))
|
||||
return var
|
||||
|
||||
@@ -175,6 +175,6 @@ async def grove_tb6612fng_change_address_to_code(config, action_id, template_arg
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
template_channel = await cg.templatable(config[CONF_ADDRESS], args, int)
|
||||
template_channel = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_address(template_channel))
|
||||
return var
|
||||
|
||||
@@ -98,7 +98,7 @@ async def to_code(config):
|
||||
async def set_heater_level_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
level_ = await cg.templatable(config[CONF_LEVEL], args, int)
|
||||
level_ = await cg.templatable(config[CONF_LEVEL], args, cg.uint8)
|
||||
cg.add(var.set_level(level_))
|
||||
return var
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import StatelessLambdaAction
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.display import validate_rotation
|
||||
import esphome.config_validation as cv
|
||||
@@ -201,7 +202,7 @@ def _validate_rotation(value):
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.display.set_rotation",
|
||||
ObjUpdateAction,
|
||||
StatelessLambdaAction,
|
||||
cv.maybe_simple_value(
|
||||
LVGL_SCHEMA.extend(
|
||||
{
|
||||
@@ -214,8 +215,7 @@ def _validate_rotation(value):
|
||||
)
|
||||
async def lvgl_set_rotation(config, action_id, template_arg, args):
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
async with LambdaContext() as context:
|
||||
add_line_marks(where=action_id)
|
||||
async with LambdaContext(args, where=action_id) as context:
|
||||
lv_add(lv_comp.set_rotation(config[CONF_ROTATION]))
|
||||
return cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUTTON,
|
||||
CONF_ID,
|
||||
CONF_INDEX,
|
||||
CONF_ITEMS,
|
||||
@@ -73,7 +74,7 @@ class TabviewType(WidgetType):
|
||||
)
|
||||
|
||||
def get_uses(self):
|
||||
return CONF_BUTTONMATRIX, TYPE_FLEX
|
||||
return CONF_BUTTONMATRIX, TYPE_FLEX, CONF_BUTTON
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
await w.set_property(
|
||||
|
||||
@@ -160,6 +160,6 @@ async def to_code(config):
|
||||
async def set_total_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32)
|
||||
cg.add(var.set_total_pulses(template_))
|
||||
return var
|
||||
|
||||
@@ -110,6 +110,6 @@ async def to_code(config):
|
||||
async def set_total_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint32)
|
||||
cg.add(var.set_total_pulses(template_))
|
||||
return var
|
||||
|
||||
@@ -129,6 +129,6 @@ async def to_code(config):
|
||||
async def sensor_template_publish_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, int)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.int32)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@@ -2207,7 +2207,11 @@ json::SerializationBuffer<> WebServer::update_json_(update::UpdateEntity *obj, J
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root[ESPHOME_F("current_version")] = obj->update_info.current_version;
|
||||
root[ESPHOME_F("title")] = obj->update_info.title;
|
||||
root[ESPHOME_F("summary")] = obj->update_info.summary;
|
||||
// Truncate long changelogs — full text available via release_url
|
||||
constexpr size_t max_summary_len = 256;
|
||||
root[ESPHOME_F("summary")] = obj->update_info.summary.size() <= max_summary_len
|
||||
? obj->update_info.summary
|
||||
: obj->update_info.summary.substr(0, max_summary_len);
|
||||
root[ESPHOME_F("release_url")] = obj->update_info.release_url;
|
||||
this->add_sorting_info_(root, obj);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ std_string = std_ns.class_("string")
|
||||
std_string_ref = std_ns.namespace("string &")
|
||||
std_vector = std_ns.class_("vector")
|
||||
std_span = std_ns.class_("span")
|
||||
int8 = global_ns.namespace("int8_t")
|
||||
uint8 = global_ns.namespace("uint8_t")
|
||||
uint16 = global_ns.namespace("uint16_t")
|
||||
uint32 = global_ns.namespace("uint32_t")
|
||||
|
||||
@@ -288,6 +288,9 @@ def merge_component_configs(
|
||||
for pkg_name, pkg_value in list(packages_value.items()):
|
||||
if pkg_name in common_bus_packages:
|
||||
continue
|
||||
# Resolve deferred !include files before checking type
|
||||
if isinstance(pkg_value, yaml_util.IncludeFile):
|
||||
pkg_value = pkg_value.load()
|
||||
if not isinstance(pkg_value, dict):
|
||||
continue
|
||||
# Component-specific package - expand its content into top level
|
||||
@@ -295,6 +298,9 @@ def merge_component_configs(
|
||||
elif isinstance(packages_value, list):
|
||||
# List format - expand all package includes
|
||||
for pkg_value in packages_value:
|
||||
# Resolve deferred !include files before checking type
|
||||
if isinstance(pkg_value, yaml_util.IncludeFile):
|
||||
pkg_value = pkg_value.load()
|
||||
if not isinstance(pkg_value, dict):
|
||||
continue
|
||||
comp_data = merge_config(comp_data, pkg_value)
|
||||
|
||||
@@ -194,6 +194,7 @@ lvgl:
|
||||
text: "Close"
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.display.set_rotation: 0
|
||||
- lvgl.widget.hide: message_box
|
||||
- lvgl.style.update:
|
||||
id: style_test
|
||||
|
||||
Reference in New Issue
Block a user