Compare commits

..
Author SHA1 Message Date
J. Nick Koston fcb52d286c Merge branch 'dev' into rtl87xx-crash-handler 2026-10-26 05:51:43 -10:00
J. Nick Koston cb943c4492 [libretiny] Report the previous boot's crash on RTL8720C
Put a recorder in front of the AmebaZ2 ROM fault handlers (vectors 3 to 7 of
the RAM vector table). It saves PC, LR, SP, CFSR, the fault address and a
stack scan into .ram.bss.noinit, re-arms the watchdog for a 1 s reset (the
ROM handler spins after its dump) and continues into the ROM handler.
The next boot logs the record and the CLI decodes it, as on BK72xx.
2026-10-26 05:08:34 -10:00
35 changed files with 381 additions and 165 deletions

No files matched your search

@@ -1,7 +1,7 @@
#include "bme680_bsec.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <string>
namespace esphome::bme680_bsec {
#ifdef USE_BSEC
@@ -9,7 +9,7 @@ ESPHOME_LOG_TAG(TAG, "bme680_bsec.sensor");
static constexpr uint32_t READ_TIMEOUT_ID = 0;
PROGMEM_STRING_TABLE(IaqAccuracyStrings, "Stabilizing", "Uncertain", "Calibrating", "Calibrated");
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
std::vector<BME680BSECComponent *>
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -386,11 +386,10 @@ void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_out
switch (outputs[i].sensor_id) {
case BSEC_OUTPUT_IAQ:
case BSEC_OUTPUT_STATIC_IAQ: {
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
this->queue_push_([this, accuracy]() {
this->publish_sensor_(this->iaq_accuracy_text_sensor_,
IaqAccuracyStrings::get_progmem_str(accuracy, IaqAccuracyStrings::LAST_INDEX));
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
});
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
@@ -436,9 +435,8 @@ void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, b
sensor->publish_state(value);
}
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value) {
if (!sensor ||
(sensor->has_state() && ESPHOME_strcmp_P(sensor->state.c_str(), reinterpret_cast<ESPHOME_PGM_P>(value)) == 0)) {
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
+1 -1
View File
@@ -77,7 +77,7 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
int64_t get_time_ns_();
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
void publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value);
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
// memory)
+4 -7
View File
@@ -2,7 +2,6 @@
#include "cc1101pa.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cmath>
#include <cstring>
@@ -15,9 +14,6 @@ namespace esphome::cc1101 {
ESPHOME_LOG_TAG(TAG, "cc1101");
static constexpr size_t TX_FIFO_SIZE = 64;
// MDMCFG2 MOD_FORMAT values
PROGMEM_STRING_TABLE(ModulationStrings, "2-FSK", "GFSK", "UNUSED", "ASK/OOK", "4-FSK", "UNUSED", "UNUSED", "MSK");
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
int e_tmp;
float m_tmp = std::frexp(value, &e_tmp);
@@ -242,6 +238,8 @@ void CC1101Component::loop() {
}
void CC1101Component::dump_config() {
static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK",
"4-FSK", "UNUSED", "UNUSED", "MSK"};
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
XTAL_FREQUENCY / (1 << 16);
float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY;
@@ -255,9 +253,8 @@ void CC1101Component::dump_config() {
" Symbol Rate: %.0f baud\n"
" Filter Bandwidth: %.1f Hz\n"
" Output Power: %.1f dBm",
this->chip_id_, freq, this->state_.CHANNR,
LOG_STR_ARG(ModulationStrings::get_log_str(this->state_.MOD_FORMAT & 0x07, 0)), symbol_rate, bw,
this->output_power_effective_);
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
symbol_rate, bw, this->output_power_effective_);
LOG_PIN(" CS Pin: ", this->cs_);
}
+15 -12
View File
@@ -4,18 +4,21 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::epaper_spi {
ESPHOME_LOG_TAG(TAG, "epaper_spi");
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
PROGMEM_STRING_TABLE(EPaperStateStrings, "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", "Unknown");
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
};
const LogString *EPaperBase::epaper_state_to_string_() {
return EPaperStateStrings::get_log_str(static_cast<uint8_t>(this->state_), EPaperStateStrings::LAST_INDEX);
const char *EPaperBase::epaper_state_to_string_() {
if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
return EPAPER_STATE_STRINGS[idx];
return "Unknown";
}
void EPaperBase::setup() {
@@ -156,7 +159,7 @@ void EPaperBase::update_effective_transform_() {
void EPaperBase::update() {
if (this->state_ != EPaperState::IDLE) {
ESP_LOGE(TAG, "Display already in state %s", LOG_STR_ARG(epaper_state_to_string_()));
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
return;
}
this->set_state_(EPaperState::UPDATE);
@@ -209,7 +212,7 @@ void EPaperBase::loop() {
this->high_frequency_loop_.stop();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
if (now - this->waiting_for_idle_last_print_ >= 1000) {
ESP_LOGV(TAG, "Waiting for idle in state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
this->waiting_for_idle_last_print_ = millis();
}
#endif
@@ -224,7 +227,7 @@ void EPaperBase::loop() {
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
void EPaperBase::abandon_update_() {
ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
LOG_STR_ARG(this->epaper_state_to_string_()));
this->epaper_state_to_string_());
this->status_set_warning(LOG_STR("Busy pin timeout"));
if (this->state_ == EPaperState::TRANSFER_DATA)
this->abort_transfer();
@@ -244,10 +247,10 @@ void EPaperBase::abandon_update_() {
* Should a subclassed class need to override this, the method will need to be made virtual.
*/
void EPaperBase::process_state_() {
ESP_LOGV(TAG, "Process state entered in state %s", LOG_STR_ARG(epaper_state_to_string_()));
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
switch (this->state_) {
default:
ESP_LOGE(TAG, "Display is in unhandled state %s", LOG_STR_ARG(epaper_state_to_string_()));
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
this->set_state_(EPaperState::IDLE);
break;
case EPaperState::IDLE:
@@ -339,7 +342,7 @@ void EPaperBase::process_state_() {
}
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
this->state_ = state;
// allow subclasses to nominate delays
if (delay == 0)
@@ -347,7 +350,7 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
this->next_delay_ = 0;
this->delay_until_ = millis() + delay;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", LOG_STR_ARG(this->epaper_state_to_string_()), delay,
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
TRUEFALSE(this->waiting_for_idle_));
// Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
if (state == EPaperState::IDLE) {
+1 -1
View File
@@ -125,7 +125,7 @@ class EPaperBase : public Display,
bool is_using_partial_update_() const { return this->full_update_every_ > 1; }
void process_state_();
const LogString *epaper_state_to_string_();
const char *epaper_state_to_string_();
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
+9 -12
View File
@@ -1,14 +1,14 @@
#include "ezo.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/progmem.h"
namespace esphome::ezo {
PROGMEM_STRING_TABLE(EzoCommandTypeStrings, "EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION", "EZO_SLOPE",
"EZO_CALIBRATION", "EZO_SLEEP", "EZO_I2C", "EZO_T", "EZO_CUSTOM");
static const char *const EZO_COMMAND_TYPE_STRINGS[] = {"EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION",
"EZO_SLOPE", "EZO_CALIBRATION", "EZO_SLEEP",
"EZO_I2C", "EZO_T", "EZO_CUSTOM"};
PROGMEM_STRING_TABLE(EzoCalibrationTypeStrings, "LOW", "MID", "HIGH");
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
void EZOSensor::dump_config() {
LOG_SENSOR("", "EZO", this);
@@ -103,8 +103,7 @@ void EZOSensor::loop() {
break;
}
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1],
LOG_STR_ARG(EzoCommandTypeStrings::get_log_str(to_run->command_type, EzoCommandTypeStrings::LAST_INDEX)));
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
if (buf[0] == 1) {
std::string payload = reinterpret_cast<char *>(&buf[1]);
@@ -169,9 +168,7 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
char payload[24];
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%s,%0.2f"),
LOG_STR_ARG(EzoCalibrationTypeStrings::get_log_str(type, EzoCalibrationTypeStrings::LAST_INDEX)),
value);
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
@@ -179,7 +176,7 @@ void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) {
// max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("I2C,%u"), address);
snprintf(payload, sizeof(payload), "I2C,%u", address);
this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C);
} else {
@@ -200,7 +197,7 @@ void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
void EZOSensor::set_t(float value) {
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
char payload[16];
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("T,%0.2f"), value);
snprintf(payload, sizeof(payload), "T,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_T);
}
@@ -223,7 +220,7 @@ void EZOSensor::set_calibration_point_high(float value) {
void EZOSensor::set_calibration_generic(float value) {
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
char payload[16];
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%0.2f"), value);
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
+24 -14
View File
@@ -6,7 +6,6 @@
#include "esphome/components/wifi/wifi_component.h"
#endif
#include "haier_base.h"
#include "esphome/core/progmem.h"
using namespace esphome::climate;
using namespace esphome::uart;
@@ -20,16 +19,28 @@ constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
PROGMEM_STRING_TABLE(ProtocolPhaseStrings, "SENDING_INIT_1", "SENDING_INIT_2", "SENDING_FIRST_STATUS_REQUEST",
"SENDING_FIRST_ALARM_STATUS_REQUEST", "IDLE", "SENDING_STATUS_REQUEST",
"SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_SIGNAL_LEVEL", "SENDING_CONTROL",
"SENDING_ACTION_COMMAND", "SENDING_ALARM_STATUS_REQUEST", "UNKNOWN");
const LogString *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
static_assert(ProtocolPhaseStrings::COUNT == ((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1,
"ProtocolPhaseStrings must match the enum ProtocolPhases, with UNKNOWN last");
// UNKNOWN (-1) wraps past the table and reads as the last entry, "UNKNOWN"
return ProtocolPhaseStrings::get_log_str(static_cast<uint8_t>(phase), ProtocolPhaseStrings::LAST_INDEX);
const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
static const char *phase_names[] = {
"SENDING_INIT_1",
"SENDING_INIT_2",
"SENDING_FIRST_STATUS_REQUEST",
"SENDING_FIRST_ALARM_STATUS_REQUEST",
"IDLE",
"SENDING_STATUS_REQUEST",
"SENDING_UPDATE_SIGNAL_REQUEST",
"SENDING_SIGNAL_LEVEL",
"SENDING_CONTROL",
"SENDING_ACTION_COMMAND",
"SENDING_ALARM_STATUS_REQUEST",
"UNKNOWN" // Should be the last!
};
static_assert(
(sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1),
"Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases");
int phase_index = (int) phase;
if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0))
phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES;
return phase_names[phase_index];
}
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
@@ -60,8 +71,7 @@ HaierClimateBase::~HaierClimateBase() {}
void HaierClimateBase::set_phase(ProtocolPhases phase) {
if (this->protocol_phase_ != phase) {
ESP_LOGV(TAG, "Phase transition: %s => %s", LOG_STR_ARG(phase_to_string_(this->protocol_phase_)),
LOG_STR_ARG(phase_to_string_(phase)));
ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
this->protocol_phase_ = phase;
}
}
@@ -215,7 +225,7 @@ haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_hand
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
phase_to_string_(this->protocol_phase_));
if (this->protocol_phase_ > ProtocolPhases::IDLE) {
this->set_phase(ProtocolPhases::IDLE);
} else {
+1 -1
View File
@@ -95,7 +95,7 @@ class HaierClimateBase : public esphome::Component,
SENDING_ALARM_STATUS_REQUEST,
NUM_PROTOCOL_PHASES
};
const LogString *phase_to_string_(ProtocolPhases phase);
const char *phase_to_string_(ProtocolPhases phase);
virtual void set_handlers() = 0;
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
+1 -1
View File
@@ -564,7 +564,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
default:
// Shouldn't get here
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
this->set_phase(ProtocolPhases::SENDING_INIT_1);
break;
}
@@ -93,7 +93,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc
if (this->protocol_phase_ >= ProtocolPhases::IDLE)
return HaierClimateBase::timeout_default_handler_(message_type);
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
phase_to_string_(this->protocol_phase_));
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
new_phase = ProtocolPhases::SENDING_INIT_1;
@@ -217,7 +217,7 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
default:
// Shouldn't get here
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
this->set_phase(ProtocolPhases::SENDING_INIT_1);
break;
}
@@ -2,20 +2,11 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::kamstrup_kmp {
ESPHOME_LOG_TAG(TAG, "kamstrup_kmp");
// KMP units indexed by unit code; the empty first entry doubles as the fallback for unknown codes
PROGMEM_STRING_TABLE(KmpUnits, "", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal", "kCal", "Mcal", "Gcal",
"varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh", "GVAh", "kW", "kW", "MW", "GW", "kvar",
"kvar", "Mvar", "Gvar", "VA", "kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l", "m3",
"l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd", "mm:dd", "",
"bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield", "s", "ms", "days",
"RTC-Q", "Datetime");
void KamstrupKMPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
LOG_UPDATE_INTERVAL(this);
@@ -246,6 +237,8 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_
}
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
const char *unit = unit_idx < sizeof(UNITS) / sizeof(UNITS[0]) ? UNITS[unit_idx] : "";
// Standard sensors
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
this->heat_energy_sensor_->publish_state(value);
@@ -270,8 +263,7 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
}
}
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value,
LOG_STR_ARG(KmpUnits::get_log_str(unit_idx, 0)));
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
}
} // namespace esphome::kamstrup_kmp
@@ -63,6 +63,16 @@ static const uint16_t CMD_TEMP_DIFF = 0x0059;
static const uint16_t CMD_FLOW = 0x004A;
static const uint16_t CMD_VOLUME = 0x0044;
// KMP units
static const char *const UNITS[] = {
"", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal",
"kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh",
"GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA",
"kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l",
"m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd",
"mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield",
"s", "ms", "days", "RTC-Q", "Datetime"};
class KamstrupKMPComponent final : public PollingComponent, public uart::UARTDevice {
public:
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
+6 -2
View File
@@ -45,6 +45,7 @@ from .const import (
FAMILY_COMPONENT,
FAMILY_FRIENDLY,
FAMILY_RTL8710B,
FAMILY_RTL8720C,
KEY_BOARD,
KEY_COMPONENT,
KEY_COMPONENT_DATA,
@@ -59,6 +60,8 @@ 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"]
@@ -554,10 +557,11 @@ 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 (FAMILY_BK7231N, FAMILY_BK7238):
# Crash handler (crash_handler.cpp, patch_noinit.py.script)
if config[CONF_FAMILY] in CRASH_HANDLER_FAMILIES:
# Crash handler (crash_handler.cpp)
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"])
+182 -44
View File
@@ -1,4 +1,4 @@
#ifdef USE_BK72XX
#ifdef USE_LIBRETINY
#include "esphome/core/defines.h"
#ifdef USE_LIBRETINY_CRASH_HANDLER
@@ -13,6 +13,7 @@
#include <cstdint>
#include <cstdio>
#ifdef USE_BK72XX
extern "C" {
#include <arm_mcu_pub.h> // MCU_REG_BACKUP_SP_*
#include <intc.h> // struct arm_registers
@@ -39,6 +40,19 @@ 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 {
@@ -47,50 +61,39 @@ 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;
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;
// 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");
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(".noinit"))) static CrashRecord s_record;
__attribute__((section(CRASH_RECORD_SECTION))) 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)
@@ -112,6 +115,9 @@ 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);
@@ -129,15 +135,61 @@ void crash_handler_log() {
ESP_LOGE(TAG, "%s", hint);
}
// --- Crash time: exception mode, interrupts masked, a few dozen bytes of stack: no printing, no
// large locals.
// --- Crash time: exception mode, 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 {
@@ -174,21 +226,9 @@ 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;
// 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;
s_record.fault_status = 0;
s_record.fault_address = 0;
finish_record(sp, SRAM_START, SRAM_END);
lt_wdt_enable(CRASH_REBOOT_WDT_MS);
}
@@ -211,7 +251,105 @@ 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_BK72XX
#endif // USE_LIBRETINY
+7 -2
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_BK72XX
#ifdef USE_LIBRETINY
#include "esphome/core/defines.h"
@@ -14,7 +14,12 @@ 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_BK72XX
#endif // USE_LIBRETINY
@@ -152,6 +152,7 @@ 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",
}
+4
View File
@@ -1,6 +1,7 @@
#ifdef USE_LIBRETINY
#include "core.h"
#include "crash_handler.h"
#include "esphome/core/hal.h"
#include "preferences.h"
@@ -27,6 +28,9 @@ 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,13 +1,9 @@
#include "micronova_text_sensor.h"
#include "esphome/core/progmem.h"
namespace esphome::micronova {
ESPHOME_LOG_TAG(TAG, "micronova.text_sensor");
PROGMEM_STRING_TABLE(StoveStateStrings, "Off", "Start", "Pellets loading", "Ignition", "Working", "Brazier Cleaning",
"Final Cleaning", "Standby", "No pellets alarm", "No ignition alarm", "Undefined alarm");
void MicroNovaTextSensor::dump_config() {
LOG_TEXT_SENSOR("", "Micronova text sensor", this);
this->dump_base_config();
@@ -19,9 +15,7 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
return;
}
// Values past the table read as "Undefined alarm"
this->publish_state(
StoveStateStrings::get_progmem_str(static_cast<uint8_t>(value_from_stove), StoveStateStrings::LAST_INDEX));
this->publish_state(STOVE_STATES[value_from_stove]);
}
} // namespace esphome::micronova
@@ -5,6 +5,18 @@
namespace esphome::micronova {
static const char *const STOVE_STATES[11] = {"Off",
"Start",
"Pellets loading",
"Ignition",
"Working",
"Brazier Cleaning",
"Final Cleaning",
"Standby",
"No pellets alarm",
"No ignition alarm",
"Undefined alarm"};
class MicroNovaTextSensor final : public text_sensor::TextSensor, public MicroNovaListener {
public:
MicroNovaTextSensor(MicroNova *m) : MicroNovaListener(m) {}
@@ -1,6 +1,5 @@
#include "pulse_counter_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#ifdef HAS_PCNT
#include <driver/gpio.h>
@@ -12,7 +11,7 @@ namespace esphome::pulse_counter {
ESPHOME_LOG_TAG(TAG, "pulse_counter");
PROGMEM_STRING_TABLE(EdgeModeStrings, "DISABLE", "INCREMENT", "DECREMENT");
const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"};
#ifdef HAS_PCNT
PulseCounterStorageBase *get_storage(bool hw_pcnt) {
@@ -228,9 +227,8 @@ void PulseCounterSensor::dump_config() {
" Rising Edge: %s\n"
" Falling Edge: %s\n"
" Filtering pulses shorter than %" PRIu32 " µs",
LOG_STR_ARG(EdgeModeStrings::get_log_str(this->storage_.rising_edge_mode, 0)),
LOG_STR_ARG(EdgeModeStrings::get_log_str(this->storage_.falling_edge_mode, 0)),
this->storage_.filter_us);
EDGE_MODE_TO_STRING[this->storage_.rising_edge_mode],
EDGE_MODE_TO_STRING[this->storage_.falling_edge_mode], this->storage_.filter_us);
LOG_UPDATE_INTERVAL(this);
}
+1
View File
@@ -29,6 +29,7 @@ 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
@@ -2,7 +2,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <utility>
@@ -12,11 +11,6 @@ ESPHOME_LOG_TAG(TAG, "seeed_mr24hpc1");
static constexpr uint32_t STARTUP_DELAY_MS = 1000;
#ifdef USE_TEXT_SENSOR
PROGMEM_STRING_TABLE(MotionStatusStrings, "None", "Motionless", "Active");
PROGMEM_STRING_TABLE(KeepAwayStrings, "None", "Close", "Away");
#endif
// Prints the component's configuration data. dump_config() prints all of the component's configuration
// items in an easy-to-read format, including the configuration key-value pairs.
void MR24HPC1Component::dump_config() {
@@ -440,9 +434,10 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da
}
break;
case 0x83:
// 0 to 3 m in steps of 0.5 m
if (this->custom_presence_of_detection_sensor_ != nullptr && data[FRAME_DATA_INDEX] <= 6) {
this->custom_presence_of_detection_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
if (this->custom_presence_of_detection_sensor_ != nullptr &&
data[FRAME_DATA_INDEX] < std::size(S_PRESENCE_OF_DETECTION_RANGE_STR)) {
this->custom_presence_of_detection_sensor_->publish_state(
S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
}
break;
case 0x84:
@@ -460,9 +455,8 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da
case 0x06:
case 0x86:
// none:0x00 close_to:0x01 far_away:0x02
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < KeepAwayStrings::COUNT)) {
this->keep_away_text_sensor_->publish_state(
KeepAwayStrings::get_progmem_str(data[FRAME_DATA_INDEX], KeepAwayStrings::LAST_INDEX));
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
}
break;
#endif
@@ -635,8 +629,8 @@ void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) {
#ifdef USE_BINARY_SENSOR
case 0x01:
case 0x81:
if (this->has_target_binary_sensor_ != nullptr && data[FRAME_DATA_INDEX] <= 1) {
this->has_target_binary_sensor_->publish_state(data[FRAME_DATA_INDEX] == 1);
if (this->has_target_binary_sensor_ != nullptr && data[FRAME_DATA_INDEX] < std::size(S_SOMEONE_EXISTS_STR)) {
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
}
break;
#endif
@@ -651,17 +645,15 @@ void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) {
#ifdef USE_TEXT_SENSOR
case 0x02:
case 0x82:
if ((this->motion_status_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < MotionStatusStrings::COUNT)) {
this->motion_status_text_sensor_->publish_state(
MotionStatusStrings::get_progmem_str(data[FRAME_DATA_INDEX], MotionStatusStrings::LAST_INDEX));
if ((this->motion_status_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
}
break;
case 0x0B:
case 0x8B:
// none:0x00 close_to:0x01 far_away:0x02
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < KeepAwayStrings::COUNT)) {
this->keep_away_text_sensor_->publish_state(
KeepAwayStrings::get_progmem_str(data[FRAME_DATA_INDEX], KeepAwayStrings::LAST_INDEX));
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
}
break;
#endif
@@ -82,6 +82,16 @@ enum OutputSwitch {
OUTPUT_SWTICH_OFF,
};
static const char *const S_SCENE_STR[5] = {"None", "Living Room", "Bedroom", "Washroom", "Area Detection"};
static const bool S_SOMEONE_EXISTS_STR[2] = {false, true};
static const char *const S_MOTION_STATUS_STR[3] = {"None", "Motionless", "Active"};
static const char *const S_KEEP_AWAY_STR[3] = {"None", "Close", "Away"};
static const char *const S_UNMANNED_TIME_STR[9] = {"None", "10s", "30s", "1min", "2min",
"5min", "10min", "30min", "60min"};
static const char *const S_BOUNDARY_STR[10] = {"0.5m", "1.0m", "1.5m", "2.0m", "2.5m",
"3.0m", "3.5m", "4.0m", "4.5m", "5.0m"}; // uint: m
static const float S_PRESENCE_OF_DETECTION_RANGE_STR[7] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f}; // uint: m
class MR24HPC1Component final : public Component,
public uart::UARTDevice { // The class name must be the name defined by text_sensor.py
#ifdef USE_TEXT_SENSOR
+1 -1
View File
@@ -774,7 +774,7 @@
#endif
#ifdef USE_LIBRETINY
#ifdef USE_BK72XX
#if defined(USE_BK72XX) || defined(USE_RTL87XX)
#define USE_LIBRETINY_CRASH_HANDLER
#endif
#define USE_BK72XX_BLE
+2
View File
@@ -28,6 +28,7 @@ from esphome.const import (
PLATFORM_ESP8266,
PLATFORM_NRF52,
PLATFORM_RP2,
PLATFORM_RTL87XX,
Platform,
)
@@ -64,6 +65,7 @@ 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:",
}
+1 -1
View File
@@ -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 and bk72xx."""
"""Decode a crash handler report with addr2line; the process_stacktrace hook of rp2, bk72xx and rtl87xx."""
if CRASH_HANDLER_RE.search(line):
_LOGGER.error("Crash detected - decoding addresses")
return True
+1
View File
@@ -57,6 +57,7 @@ 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,
@@ -0,0 +1,5 @@
esphome:
name: test
rtl87xx:
board: generic-rtl8710bn-2mb-788k
@@ -0,0 +1,5 @@
esphome:
name: test
rtl87xx:
board: bw15
@@ -1,4 +1,4 @@
"""Only BK7238 and BK7231N, the BK chips with an ITCM linker region, get the crash handler."""
"""The crash handler is built for BK7238, BK7231N (both with an ITCM linker region) and RTL8720C."""
from __future__ import annotations
@@ -12,12 +12,14 @@ from esphome.core import CORE
@pytest.mark.parametrize(
("config", "enabled"),
("config", "enabled", "bk_hooks"),
[
("bk7238.yaml", True),
("bk72xx.yaml", True), # BK7231N
("bk7231t.yaml", False),
("ln882x.yaml", False),
("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),
],
)
def test_crash_handler_family_gate(
@@ -25,13 +27,14 @@ 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 enabled
assert (f"-Wl,--wrap={trap}" in CORE.build_flags) is bk_hooks
assert (
"pre:patch_noinit.py" in CORE.platformio_options["extra_scripts"]
) is enabled
) is bk_hooks
@@ -0,0 +1,13 @@
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);
@@ -0,0 +1,15 @@
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
+2 -2
View 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_RTL87XX}
CORE.data[KEY_CORE] = {KEY_TARGET_PLATFORM: PLATFORM_LN882X}
config = {
CONF_LOGGER: {
CONF_BAUD_RATE: 115200,
+2 -2
View File
@@ -15,7 +15,7 @@ from unittest.mock import Mock
import pytest
from esphome import platform_hooks
from esphome.const import PLATFORM_ESP32, PLATFORM_RTL87XX, Platform
from esphome.const import PLATFORM_ESP32, PLATFORM_LN882X, 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_RTL87XX) is None
assert platform_hooks.get_stacktrace_handler(PLATFORM_LN882X) 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)
+7 -1
View File
@@ -18,6 +18,7 @@ from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2,
PLATFORM_RTL87XX,
@@ -66,6 +67,10 @@ 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": [
@@ -231,6 +236,7 @@ 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"],
}
@@ -548,7 +554,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_RTL87XX)
processor = stacktrace.LogLineProcessor(CONFIG, PLATFORM_LN882X)
processor.process_line("PC: 0x40104960")
mock_import.assert_not_called()