Compare commits

..
Author SHA1 Message Date
J. Nick Koston 5cceddfbf4 [kamstrup_kmp] Keep the unit name table in flash (#20642) 2026-10-26 11:58:43 -04:00
J. Nick Koston ff6a37a052 [micronova] Keep the stove state table in flash (#20645) 2026-10-26 11:58:15 -04:00
J. Nick Koston 6990092be0 [ezo] Keep the command and calibration type tables in flash (#20655) 2026-10-26 11:57:41 -04:00
J. Nick Koston 9220c79a51 [bme680_bsec] Keep the IAQ accuracy names in flash (#20656) 2026-10-26 11:57:23 -04:00
J. Nick Koston 215fcd4f5d [pulse_counter] Keep the edge mode name table in flash (#20660) 2026-10-26 11:57:03 -04:00
J. Nick Koston 918e3b3a58 [haier] Keep the protocol phase names in flash (#20710) 2026-10-26 11:56:43 -04:00
J. Nick Koston 347eb3902e [seeed_mr24hpc1] Keep the text sensor state tables in flash (#20711) 2026-10-26 11:55:18 -04:00
J. Nick Koston 177820083f [epaper_spi] Keep the state name table in flash (#20713) 2026-10-26 11:54:07 -04:00
J. Nick Koston b0a0524091 [cc1101] Keep the modulation name table in flash (#20716) 2026-10-26 11:53:10 -04:00
93b6388cb9 [sx127x] Keep the bandwidth and ramp tables in flash (#20650)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-26 11:50:47 -04:00
J. Nick Koston 8677844629 [ld2410] Format the gate threshold test (#21649) 2026-10-26 05:50:10 -10:00
c1a9c8f2f1 [sx126x] Keep the bandwidth and ramp tables in flash (#20647)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-26 15:49:44 +00:00
J. Nick Koston f35e45febf [ld2412] Send the output pin level when its select changes (#21353) 2026-10-26 11:46:42 -04:00
28 changed files with 459 additions and 185 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 <string>
#include "esphome/core/progmem.h"
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;
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
PROGMEM_STRING_TABLE(IaqAccuracyStrings, "Stabilizing", "Uncertain", "Calibrating", "Calibrated");
std::vector<BME680BSECComponent *>
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -386,10 +386,11 @@ 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, std::size(IAQ_ACCURACY_STATES) - 1);
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
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_, IAQ_ACCURACY_STATES[accuracy]);
this->publish_sensor_(this->iaq_accuracy_text_sensor_,
IaqAccuracyStrings::get_progmem_str(accuracy, IaqAccuracyStrings::LAST_INDEX));
});
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
@@ -435,8 +436,9 @@ void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, b
sensor->publish_state(value);
}
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
if (!sensor || (sensor->has_state() && sensor->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)) {
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, const std::string &value);
void publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value);
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
// memory)
+7 -4
View File
@@ -2,6 +2,7 @@
#include "cc1101pa.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cmath>
#include <cstring>
@@ -14,6 +15,9 @@ 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);
@@ -238,8 +242,6 @@ 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;
@@ -253,8 +255,9 @@ 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, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
symbol_rate, bw, this->output_power_effective_);
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_);
LOG_PIN(" CS Pin: ", this->cs_);
}
+12 -15
View File
@@ -4,21 +4,18 @@
#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;
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",
};
PROGMEM_STRING_TABLE(EPaperStateStrings, "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", "Unknown");
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";
const LogString *EPaperBase::epaper_state_to_string_() {
return EPaperStateStrings::get_log_str(static_cast<uint8_t>(this->state_), EPaperStateStrings::LAST_INDEX);
}
void EPaperBase::setup() {
@@ -159,7 +156,7 @@ void EPaperBase::update_effective_transform_() {
void EPaperBase::update() {
if (this->state_ != EPaperState::IDLE) {
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
ESP_LOGE(TAG, "Display already in state %s", LOG_STR_ARG(epaper_state_to_string_()));
return;
}
this->set_state_(EPaperState::UPDATE);
@@ -212,7 +209,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", this->epaper_state_to_string_());
ESP_LOGV(TAG, "Waiting for idle in state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
this->waiting_for_idle_last_print_ = millis();
}
#endif
@@ -227,7 +224,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_,
this->epaper_state_to_string_());
LOG_STR_ARG(this->epaper_state_to_string_()));
this->status_set_warning(LOG_STR("Busy pin timeout"));
if (this->state_ == EPaperState::TRANSFER_DATA)
this->abort_transfer();
@@ -247,10 +244,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", epaper_state_to_string_());
ESP_LOGV(TAG, "Process state entered in state %s", LOG_STR_ARG(epaper_state_to_string_()));
switch (this->state_) {
default:
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
ESP_LOGE(TAG, "Display is in unhandled state %s", LOG_STR_ARG(epaper_state_to_string_()));
this->set_state_(EPaperState::IDLE);
break;
case EPaperState::IDLE:
@@ -342,7 +339,7 @@ void EPaperBase::process_state_() {
}
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
ESP_LOGV(TAG, "Exit state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
this->state_ = state;
// allow subclasses to nominate delays
if (delay == 0)
@@ -350,7 +347,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", this->epaper_state_to_string_(), delay,
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", LOG_STR_ARG(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 char *epaper_state_to_string_();
const LogString *epaper_state_to_string_();
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
+12 -9
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 {
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(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_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
PROGMEM_STRING_TABLE(EzoCalibrationTypeStrings, "LOW", "MID", "HIGH");
void EZOSensor::dump_config() {
LOG_SENSOR("", "EZO", this);
@@ -103,7 +103,8 @@ void EZOSensor::loop() {
break;
}
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
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)));
if (buf[0] == 1) {
std::string payload = reinterpret_cast<char *>(&buf[1]);
@@ -168,7 +169,9 @@ 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];
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%s,%0.2f"),
LOG_STR_ARG(EzoCalibrationTypeStrings::get_log_str(type, EzoCalibrationTypeStrings::LAST_INDEX)),
value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
@@ -176,7 +179,7 @@ void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) {
// max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
snprintf(payload, sizeof(payload), "I2C,%u", address);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("I2C,%u"), address);
this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C);
} else {
@@ -197,7 +200,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];
snprintf(payload, sizeof(payload), "T,%0.2f", value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("T,%0.2f"), value);
this->add_command_(payload, EzoCommandType::EZO_T);
}
@@ -220,7 +223,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];
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%0.2f"), value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
+14 -24
View File
@@ -6,6 +6,7 @@
#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;
@@ -19,28 +20,16 @@ constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
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];
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);
}
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
@@ -71,7 +60,8 @@ HaierClimateBase::~HaierClimateBase() {}
void HaierClimateBase::set_phase(ProtocolPhases phase) {
if (this->protocol_phase_ != phase) {
ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
ESP_LOGV(TAG, "Phase transition: %s => %s", LOG_STR_ARG(phase_to_string_(this->protocol_phase_)),
LOG_STR_ARG(phase_to_string_(phase)));
this->protocol_phase_ = phase;
}
}
@@ -225,7 +215,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,
phase_to_string_(this->protocol_phase_));
LOG_STR_ARG(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 char *phase_to_string_(ProtocolPhases phase);
const LogString *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",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
LOG_STR_ARG(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,
phase_to_string_(this->protocol_phase_));
LOG_STR_ARG(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",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
this->set_phase(ProtocolPhases::SENDING_INIT_1);
break;
}
+3 -4
View File
@@ -1,7 +1,8 @@
"""Shared base for the infrared and radio_frequency entity components."""
import esphome.codegen as cg
from esphome.const import CONF_API
from esphome.components import remote_base
from esphome.components.api import DOMAIN as API_DOMAIN
from esphome.core import CORE
from esphome.core.entity_helpers import queue_entity_register
from esphome.types import ConfigType
@@ -23,10 +24,8 @@ async def attach_transmitter(var: cg.MockObj, config: ConfigType, key: str) -> N
does. Without this call anywhere in the build a request is answered as
soon as the frame is handed over.
"""
from esphome.components import remote_base
await remote_base.register_transmittable(var, config, key)
if CONF_API in CORE.config:
if API_DOMAIN in CORE.config:
cg.add_define("USE_IR_RF_TRANSMIT_COMPLETE")
@@ -2,11 +2,20 @@
#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);
@@ -237,8 +246,6 @@ 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);
@@ -263,7 +270,8 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
}
}
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value,
LOG_STR_ARG(KmpUnits::get_log_str(unit_idx, 0)));
}
} // namespace esphome::kamstrup_kmp
@@ -63,16 +63,6 @@ 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; }
+83 -32
View File
@@ -79,7 +79,6 @@ static constexpr uint8_t OUT_PIN_LEVELS[] PROGMEM = {OUT_PIN_LEVEL_LOW, OUT_PIN_
static constexpr uint32_t BAUD_RATES[] PROGMEM = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Default used when number component is not defined
// Commands
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
@@ -384,8 +383,10 @@ static inline bool has_value(number::Number *n) { return n != nullptr && n->has_
// Timeout ids 0 (still) and 1 (motion) for a write waiting on a re-query
static constexpr uint32_t GATE_QUERY_TIMEOUT_ID = 0;
static constexpr uint32_t GATE_QUERY_TIMEOUT_MS = 2000;
#endif
// Timeout id for a basic configuration write waiting on a re-query, and how long any such write waits
static constexpr uint32_t BASIC_CONF_TIMEOUT_ID = 2;
static constexpr uint32_t QUERY_TIMEOUT_MS = 2000;
void LD2412Component::handle_ack_data_() {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
@@ -553,38 +554,44 @@ void LD2412Component::handle_ack_data_() {
break;
}
case CMD_QUERY_BASIC_CONF: // Query parameters response
{
#ifdef USE_NUMBER
case CMD_QUERY_BASIC_CONF: { // Query parameters response
if (this->buffer_pos_ < 15 + HEADER_FOOTER_SIZE) {
break; // too short to hold the configuration
}
/*
Moving distance range: 9th byte
Still distance range: 10th byte
*/
set_number_value(this->min_distance_gate_number_, this->buffer_data_[10]);
set_number_value(this->max_distance_gate_number_, this->buffer_data_[11] - 1);
ESP_LOGV(TAG, "min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->buffer_data_[10],
this->buffer_data_[11]);
/*
None Duration: 11~12th bytes
*/
set_number_value(this->timeout_number_, encode_uint16(this->buffer_data_[13], this->buffer_data_[12]));
ESP_LOGV(TAG, "timeout_number_: %u", encode_uint16(this->buffer_data_[13], this->buffer_data_[12]));
/*
Output pin configuration: 13th bytes
*/
// True only while a held-back write waits for this answer
const bool pending = this->cancel_timeout(BASIC_CONF_TIMEOUT_ID);
this->basic_conf_read_ = true;
// Min gate: 9th byte, max gate: 10th byte, timeout: 11~12th bytes, output pin level: 13th byte
this->min_gate_ = this->buffer_data_[10];
this->max_gate_ = this->buffer_data_[11];
this->timeout_ = encode_uint16(this->buffer_data_[13], this->buffer_data_[12]);
this->out_pin_level_ = this->buffer_data_[14];
ESP_LOGV(TAG, "min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->min_gate_, this->max_gate_);
ESP_LOGV(TAG, "timeout_number_: %u", this->timeout_);
// The pending write's user values must survive the answer
#ifdef USE_NUMBER
if (!pending || !has_value(this->min_distance_gate_number_)) {
set_number_value(this->min_distance_gate_number_, this->min_gate_);
}
if (!pending || !has_value(this->max_distance_gate_number_)) {
set_number_value(this->max_distance_gate_number_, this->max_gate_ - 1);
}
if (!pending || !has_value(this->timeout_number_)) {
set_number_value(this->timeout_number_, this->timeout_);
}
#endif
#ifdef USE_SELECT
if (this->out_pin_level_select_ != nullptr) {
if (this->out_pin_level_select_ != nullptr && !(pending && this->out_pin_level_select_->has_state())) {
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
this->out_pin_level_select_->publish_state(*index);
}
}
#endif
#endif
} break;
if (pending) {
this->set_basic_config();
}
break;
}
default:
break;
}
@@ -690,8 +697,9 @@ void LD2412Component::set_engineering_mode(bool enable) {
void LD2412Component::factory_reset() {
this->set_config_mode_(true);
this->send_command_(CMD_FACTORY_RESET, nullptr, 0);
// Reset discards the settings; the next write must wait for the re-read
this->basic_conf_read_ = false;
#ifdef USE_NUMBER
// Reset discards the thresholds; the next write must wait for the re-read
this->gate_move_thresholds_.read = false;
this->gate_still_thresholds_.read = false;
#endif
@@ -714,10 +722,22 @@ void LD2412Component::get_distance_resolution_() { this->send_command_(CMD_QUERY
void LD2412Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
void LD2412Component::set_basic_config() {
uint8_t min_gate = 1;
uint8_t max_gate = TOTAL_GATES;
uint16_t timeout = DEFAULT_PRESENCE_TIMEOUT;
uint8_t out_pin_level = 0x01;
// Entities that are not configured keep what the module last reported
uint8_t min_gate = this->min_gate_;
uint8_t max_gate = this->max_gate_;
uint16_t timeout = this->timeout_;
uint8_t out_pin_level = this->out_pin_level_;
bool from_module = false;
#ifdef USE_NUMBER
from_module = this->min_distance_gate_number_ == nullptr || this->max_distance_gate_number_ == nullptr ||
this->timeout_number_ == nullptr;
#else
from_module = true;
#endif
#ifndef USE_SELECT
from_module = true;
#endif
#ifdef USE_NUMBER
if (this->min_distance_gate_number_ != nullptr) {
@@ -742,8 +762,19 @@ void LD2412Component::set_basic_config() {
if (!index)
return;
out_pin_level = progmem_read_byte(&OUT_PIN_LEVELS[*index]);
} else {
from_module = true;
}
#endif
if (from_module && !this->basic_conf_read_) {
// The module never reported, so the values on hand are the built in defaults
ESP_LOGW(TAG, "Command %02X held back until the module reports its settings", CMD_BASIC_CONF);
this->set_config_mode_(true);
this->query_parameters_();
this->set_config_mode_(false);
this->set_timeout(BASIC_CONF_TIMEOUT_ID, QUERY_TIMEOUT_MS, [this]() { this->basic_conf_timeout_(); });
return;
}
uint8_t value[5] = {
lowbyte(min_gate), lowbyte(max_gate), lowbyte(timeout), highbyte(timeout), out_pin_level,
@@ -753,6 +784,26 @@ void LD2412Component::set_basic_config() {
this->set_config_mode_(false);
}
void LD2412Component::basic_conf_timeout_() {
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its settings", CMD_BASIC_CONF);
#ifdef USE_NUMBER
// The values on hand never reached the module, so forgetting them loses nothing
for (number::Number *n : {this->min_distance_gate_number_, this->max_distance_gate_number_, this->timeout_number_}) {
if (n != nullptr) {
n->publish_state(NAN);
}
}
#endif
#ifdef USE_SELECT
// Show the last known or default level until the module reports one
if (this->out_pin_level_select_ != nullptr) {
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
this->out_pin_level_select_->publish_state(*index);
}
}
#endif
}
#ifdef USE_NUMBER
void LD2412Component::send_gate_thresholds_(bool motion) {
GateThresholds &group = this->gate_thresholds_(motion);
@@ -770,9 +821,9 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
}
if (configured < TOTAL_GATES && !group.read) {
// Unread gates would go out as zero, which is maximum sensitivity
ESP_LOGW(TAG, "Command %02X held back until the module reports its gate thresholds", command);
ESP_LOGW(TAG, "Command %02X held back until the module reports its settings", command);
this->send_command_(motion ? CMD_QUERY_MOTION_GATE_SENS : CMD_QUERY_STATIC_GATE_SENS, nullptr, 0);
this->set_timeout(GATE_QUERY_TIMEOUT_ID + motion, GATE_QUERY_TIMEOUT_MS,
this->set_timeout(GATE_QUERY_TIMEOUT_ID + motion, QUERY_TIMEOUT_MS,
[this, motion]() { this->gate_query_timeout_(motion); });
return;
}
@@ -780,7 +831,7 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
}
void LD2412Component::gate_query_timeout_(bool motion) {
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its gate thresholds",
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its settings",
motion ? CMD_MOTION_GATE_SENS : CMD_STATIC_GATE_SENS);
// The values on hand never reached the module or are stale, so forgetting them loses nothing
for (number::Number *n : this->gate_thresholds_(motion).numbers) {
+8 -1
View File
@@ -35,6 +35,7 @@ using namespace ld24xx;
static constexpr uint8_t MAX_LINE_LENGTH = 54; // Max characters for serial buffer
static constexpr uint8_t TOTAL_GATES = 14; // Total number of gates supported by the LD2412
static constexpr uint16_t DEFAULT_PRESENCE_TIMEOUT = 5;
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
@@ -136,6 +137,7 @@ class LD2412Component final : public Component, public uart::UARTDevice {
void handle_periodic_data_();
void handle_ack_data_();
bool handle_frame_(uint8_t frame_len);
void basic_conf_timeout_();
void readline_(int readch);
void query_parameters_();
void get_version_();
@@ -160,7 +162,12 @@ class LD2412Component final : public Component, public uart::UARTDevice {
uint8_t light_function_ = 0;
uint8_t light_threshold_ = 0;
uint8_t out_pin_level_ = 0;
// Basic configuration as last reported by the module; resent for entities that are not configured
bool basic_conf_read_{false}; // answered since boot or the last factory reset
uint16_t timeout_ = DEFAULT_PRESENCE_TIMEOUT;
uint8_t min_gate_ = 1;
uint8_t max_gate_ = TOTAL_GATES;
uint8_t out_pin_level_ = 1;
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t mac_address_[MAC_ADDRESS_SIZE] = {0, 0, 0, 0, 0, 0};
+4 -1
View File
@@ -42,7 +42,7 @@ CONFIG_SCHEMA = {
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
LightOutControlSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
@@ -77,8 +77,11 @@ async def to_code(config: ConfigType) -> None:
hub.set_light_function_select,
options=["off", "below", "above"],
)
# The pin level travels in the basic configuration command, not the light control one
await selects(
CONF_OUT_PIN_LEVEL,
hub.set_out_pin_level_select,
options=["low", "high"],
apply="set_basic_config()",
publish="before",
)
+1 -4
View File
@@ -2,6 +2,7 @@ from collections.abc import Callable
from esphome import automation
import esphome.codegen as cg
from esphome.components import audio
from esphome.components.const import CONF_BITRATE
import esphome.config_validation as cv
from esphome.const import (
@@ -115,8 +116,6 @@ def validate_preferred_format(
"""
def validator(config: ConfigType) -> ConfigType:
from esphome.components import audio
# Inherit settings from audio device if not manually set
inherit_property_from(CONF_NUM_CHANNELS, audio_device_key)(config)
inherit_property_from(CONF_SAMPLE_RATE, audio_device_key)(config)
@@ -156,8 +155,6 @@ def request_codecs_for_format_configs(
Public API for external components. Do not remove.
"""
from esphome.components import audio
needed_formats: set[str] = set()
need_all = False
@@ -1,9 +1,13 @@
#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();
@@ -15,7 +19,9 @@ void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
return;
}
this->publish_state(STOVE_STATES[value_from_stove]);
// 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));
}
} // namespace esphome::micronova
@@ -5,18 +5,6 @@
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,5 +1,6 @@
#include "pulse_counter_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#ifdef HAS_PCNT
#include <driver/gpio.h>
@@ -11,7 +12,7 @@ namespace esphome::pulse_counter {
ESPHOME_LOG_TAG(TAG, "pulse_counter");
const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"};
PROGMEM_STRING_TABLE(EdgeModeStrings, "DISABLE", "INCREMENT", "DECREMENT");
#ifdef HAS_PCNT
PulseCounterStorageBase *get_storage(bool hw_pcnt) {
@@ -227,8 +228,9 @@ void PulseCounterSensor::dump_config() {
" Rising Edge: %s\n"
" Falling Edge: %s\n"
" Filtering pulses shorter than %" PRIu32 " µs",
EDGE_MODE_TO_STRING[this->storage_.rising_edge_mode],
EDGE_MODE_TO_STRING[this->storage_.falling_edge_mode], this->storage_.filter_us);
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);
LOG_UPDATE_INTERVAL(this);
}
@@ -2,6 +2,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <utility>
@@ -11,6 +12,11 @@ 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() {
@@ -434,10 +440,9 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da
}
break;
case 0x83:
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]]);
// 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);
}
break;
case 0x84:
@@ -455,8 +460,9 @@ 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] < 3)) {
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
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));
}
break;
#endif
@@ -629,8 +635,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] < std::size(S_SOMEONE_EXISTS_STR)) {
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
if (this->has_target_binary_sensor_ != nullptr && data[FRAME_DATA_INDEX] <= 1) {
this->has_target_binary_sensor_->publish_state(data[FRAME_DATA_INDEX] == 1);
}
break;
#endif
@@ -645,15 +651,17 @@ 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] < 3)) {
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
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));
}
break;
case 0x0B:
case 0x8B:
// none:0x00 close_to:0x01 far_away:0x02
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]]);
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));
}
break;
#endif
@@ -82,16 +82,6 @@ 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
+15 -11
View File
@@ -6,13 +6,16 @@
namespace esphome::sx126x {
ESPHOME_LOG_TAG(TAG, "sx126x");
static const uint16_t RAMP[8] = {10, 20, 40, 80, 200, 800, 1700, 3400};
static const uint32_t BW_HZ[31] = {4800, 5800, 7300, 9700, 11700, 14600, 19500, 23400, 29300, 39000, 46900,
58600, 78200, 93800, 117300, 156200, 187200, 234300, 312000, 373600, 467000, 7810,
10420, 15630, 20830, 31250, 41670, 62500, 125000, 250000, 500000};
static const uint8_t BW_LORA[10] = {LORA_BW_7810, LORA_BW_10420, LORA_BW_15630, LORA_BW_20830, LORA_BW_31250,
LORA_BW_41670, LORA_BW_62500, LORA_BW_125000, LORA_BW_250000, LORA_BW_500000};
static const uint8_t BW_FSK[21] = {
static constexpr uint16_t RAMP[8] PROGMEM = {10, 20, 40, 80, 200, 800, 1700, 3400};
// Aligned 32 bit loads from flash are safe on ESP8266, so this table is read directly
static constexpr uint32_t BW_HZ[31] PROGMEM = {4800, 5800, 7300, 9700, 11700, 14600, 19500, 23400,
29300, 39000, 46900, 58600, 78200, 93800, 117300, 156200,
187200, 234300, 312000, 373600, 467000, 7810, 10420, 15630,
20830, 31250, 41670, 62500, 125000, 250000, 500000};
static constexpr uint8_t BW_LORA[10] PROGMEM = {LORA_BW_7810, LORA_BW_10420, LORA_BW_15630, LORA_BW_20830,
LORA_BW_31250, LORA_BW_41670, LORA_BW_62500, LORA_BW_125000,
LORA_BW_250000, LORA_BW_500000};
static constexpr uint8_t BW_FSK[21] PROGMEM = {
FSK_BW_4800, FSK_BW_5800, FSK_BW_7300, FSK_BW_9700, FSK_BW_11700, FSK_BW_14600, FSK_BW_19500,
FSK_BW_23400, FSK_BW_29300, FSK_BW_39000, FSK_BW_46900, FSK_BW_58600, FSK_BW_78200, FSK_BW_93800,
FSK_BW_117300, FSK_BW_156200, FSK_BW_187200, FSK_BW_234300, FSK_BW_312000, FSK_BW_373600, FSK_BW_467000};
@@ -218,7 +221,7 @@ void SX126x::configure() {
// set modulation params
float duration = 1000.0f * (1UL << this->spreading_factor_) / BW_HZ[this->bandwidth_];
buf[0] = this->spreading_factor_;
buf[1] = BW_LORA[this->bandwidth_ - SX126X_BW_7810];
buf[1] = progmem_read_byte(&BW_LORA[this->bandwidth_ - SX126X_BW_7810]);
buf[2] = this->coding_rate_;
buf[3] = (duration > LOW_DATA_RATE_OPTIMIZE_THRESHOLD) ? 0x01 : 0x00;
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
@@ -236,7 +239,7 @@ void SX126x::configure() {
buf[1] = (bitrate >> 8) & 0xFF;
buf[2] = (bitrate >> 0) & 0xFF;
buf[3] = this->shaping_;
buf[4] = BW_FSK[this->bandwidth_ - SX126X_BW_4800];
buf[4] = progmem_read_byte(&BW_FSK[this->bandwidth_ - SX126X_BW_4800]);
buf[5] = (fdev >> 16) & 0xFF;
buf[6] = (fdev >> 8) & 0xFF;
buf[7] = (fdev >> 0) & 0xFF;
@@ -515,8 +518,9 @@ void SX126x::dump_config() {
" Payload Length: %" PRIu32 "\n"
" CRC Enable: %s\n"
" Rx Start: %s",
this->version_, this->frequency_, BW_HZ[this->bandwidth_], this->pa_power_, RAMP[this->pa_ramp_],
this->payload_length_, TRUEFALSE(this->crc_enable_), TRUEFALSE(this->rx_start_));
this->version_, this->frequency_, BW_HZ[this->bandwidth_], this->pa_power_,
progmem_read_uint16(&RAMP[this->pa_ramp_]), this->payload_length_, TRUEFALSE(this->crc_enable_),
TRUEFALSE(this->rx_start_));
if (this->modulation_ == PACKET_TYPE_GFSK) {
const char *shaping = "NONE";
if (this->shaping_ == GAUSSIAN_BT_0_3) {
+17 -13
View File
@@ -7,16 +7,19 @@ namespace esphome::sx127x {
ESPHOME_LOG_TAG(TAG, "sx127x");
static const uint32_t FXOSC = 32000000u;
static const uint16_t RAMP[16] = {3400, 2000, 1000, 500, 250, 125, 100, 62, 50, 40, 31, 25, 20, 15, 12, 10};
static const uint32_t BW_HZ[22] = {2604, 3125, 3906, 5208, 6250, 7812, 10416, 12500, 15625, 20833, 25000,
31250, 41666, 50000, 62500, 83333, 100000, 125000, 166666, 200000, 250000, 500000};
static const uint8_t BW_LORA[22] = {BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_10_4, BW_15_6,
BW_15_6, BW_20_8, BW_31_3, BW_31_3, BW_41_7, BW_62_5, BW_62_5, BW_125_0,
BW_125_0, BW_125_0, BW_250_0, BW_250_0, BW_250_0, BW_500_0};
static const uint8_t BW_FSK_OOK[22] = {RX_BW_2_6, RX_BW_3_1, RX_BW_3_9, RX_BW_5_2, RX_BW_6_3, RX_BW_7_8,
RX_BW_10_4, RX_BW_12_5, RX_BW_15_6, RX_BW_20_8, RX_BW_25_0, RX_BW_31_3,
RX_BW_41_7, RX_BW_50_0, RX_BW_62_5, RX_BW_83_3, RX_BW_100_0, RX_BW_125_0,
RX_BW_166_7, RX_BW_200_0, RX_BW_250_0, RX_BW_250_0};
static constexpr uint16_t RAMP[16] PROGMEM = {3400, 2000, 1000, 500, 250, 125, 100, 62, 50, 40, 31, 25, 20, 15, 12, 10};
// Aligned 32 bit loads from flash are safe on ESP8266, so this table is read directly
static constexpr uint32_t BW_HZ[22] PROGMEM = {2604, 3125, 3906, 5208, 6250, 7812, 10416, 12500,
15625, 20833, 25000, 31250, 41666, 50000, 62500, 83333,
100000, 125000, 166666, 200000, 250000, 500000};
static constexpr uint8_t BW_LORA[22] PROGMEM = {
BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_7_8, BW_10_4, BW_15_6, BW_15_6, BW_20_8, BW_31_3,
BW_31_3, BW_41_7, BW_62_5, BW_62_5, BW_125_0, BW_125_0, BW_125_0, BW_250_0, BW_250_0, BW_250_0, BW_500_0};
static constexpr uint8_t BW_FSK_OOK[22] PROGMEM = {
RX_BW_2_6, RX_BW_3_1, RX_BW_3_9, RX_BW_5_2, RX_BW_6_3, RX_BW_7_8, RX_BW_10_4, RX_BW_12_5,
RX_BW_15_6, RX_BW_20_8, RX_BW_25_0, RX_BW_31_3, RX_BW_41_7, RX_BW_50_0, RX_BW_62_5, RX_BW_83_3,
RX_BW_100_0, RX_BW_125_0, RX_BW_166_7, RX_BW_200_0, RX_BW_250_0, RX_BW_250_0};
static const int32_t RSSI_OFFSET_HF = 157;
static const int32_t RSSI_OFFSET_LF = 164;
@@ -135,7 +138,7 @@ void SX127x::configure() {
void SX127x::configure_fsk_ook_() {
// set the channel bw
this->write_register_(REG_RX_BW, BW_FSK_OOK[this->bandwidth_]);
this->write_register_(REG_RX_BW, progmem_read_byte(&BW_FSK_OOK[this->bandwidth_]));
// set fdev
uint32_t fdev = std::min((this->deviation_ * 4096) / 250000, (uint32_t) 0x3FFF);
@@ -211,7 +214,8 @@ void SX127x::configure_lora_() {
uint8_t header_mode = this->payload_length_ > 0 ? IMPLICIT_HEADER : EXPLICIT_HEADER;
uint8_t crc_mode = (this->crc_enable_) ? RX_PAYLOAD_CRC_ON : RX_PAYLOAD_CRC_OFF;
uint8_t spreading_factor = this->spreading_factor_ << SPREADING_FACTOR_SHIFT;
this->write_register_(REG_MODEM_CONFIG1, BW_LORA[this->bandwidth_] | this->coding_rate_ | header_mode);
this->write_register_(REG_MODEM_CONFIG1,
progmem_read_byte(&BW_LORA[this->bandwidth_]) | this->coding_rate_ | header_mode);
this->write_register_(REG_MODEM_CONFIG2, spreading_factor | crc_mode);
// config fifo and payload length
@@ -432,7 +436,7 @@ void SX127x::dump_config() {
" PA Power: %" PRIu8 " dBm\n"
" PA Ramp: %" PRIu16 " us",
TRUEFALSE(this->auto_cal_), this->frequency_, BW_HZ[this->bandwidth_], pa_pin, this->pa_power_,
RAMP[this->pa_ramp_]);
progmem_read_uint16(&RAMP[this->pa_ramp_]));
if (this->modulation_ == MOD_FSK) {
ESP_LOGCONFIG(TAG, " Deviation: %" PRIu32 " Hz", this->deviation_);
}
@@ -0,0 +1,53 @@
esphome:
name: uart-mock-ld2412-outpin
host:
api:
batch_delay: 0ms # Disable batching to receive all state updates
logger:
level: VERBOSE
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
# Dummy uart entry to satisfy ld2412's DEPENDENCIES = ["uart"]
# The actual UART bus used is the uart_mock component below
uart:
baud_rate: 115200
port: /dev/null
# Answers the basic configuration query with gates 3 to 9, a 10 second timeout and a low output pin level
uart_mock:
id: mock_uart
baud_rate: 256000
on_tx:
- then:
- lambda: |-
// The length and command word arrive as one write of their own
static const std::vector<uint8_t> BASIC_CONF_QUERY = {0x02, 0x00, 0x12, 0x00};
if (data != BASIC_CONF_QUERY) {
return;
}
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>{
0xFD, 0xFC, 0xFB, 0xFA, // header
0x09, 0x00, // length 9
0x12, 0x01, // answer to command 0x0012
0x00, 0x00, // status: success
0x03, 0x09, // min and max gate
0x0A, 0x00, // timeout 10 s
0x01, // output pin level: low
0x04, 0x03, 0x02, 0x01, // footer
});
ld2412:
id: ld2412_dev
uart_id: mock_uart
# No numbers on purpose: the select must resend what the module reported for them
select:
- platform: ld2412
ld2412_id: ld2412_dev
out_pin_level:
name: "Out Pin Level"
@@ -0,0 +1,66 @@
esphome:
name: uart-mock-ld2412-outpin-held
host:
api:
batch_delay: 0ms # Disable batching to receive all state updates
logger:
level: VERBOSE
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
# Dummy uart entry to satisfy ld2412's DEPENDENCIES = ["uart"]
# The actual UART bus used is the uart_mock component below
uart:
baud_rate: 115200
port: /dev/null
# Answers only the second basic configuration query, and only inside configuration mode, so the select
# must hold its write back and ask again the way a real module expects
uart_mock:
id: mock_uart
baud_rate: 256000
on_tx:
- then:
- lambda: |-
// The length and command word arrive as one write of their own
static const std::vector<uint8_t> BASIC_CONF_QUERY = {0x02, 0x00, 0x12, 0x00};
static const std::vector<uint8_t> ENABLE_CONF = {0x04, 0x00, 0xFF, 0x00};
static const std::vector<uint8_t> DISABLE_CONF = {0x02, 0x00, 0xFE, 0x00};
static bool config_mode = false;
static int queries = 0;
if (data == ENABLE_CONF) {
config_mode = true;
return;
}
if (data == DISABLE_CONF) {
config_mode = false;
return;
}
if (data != BASIC_CONF_QUERY || !config_mode || ++queries != 2) {
return;
}
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>{
0xFD, 0xFC, 0xFB, 0xFA, // header
0x09, 0x00, // length 9
0x12, 0x01, // answer to command 0x0012
0x00, 0x00, // status: success
0x03, 0x09, // min and max gate
0x0A, 0x00, // timeout 10 s
0x01, // output pin level: low
0x04, 0x03, 0x02, 0x01, // footer
});
ld2412:
id: ld2412_dev
uart_id: mock_uart
# No numbers on purpose: the select must resend what the module reported for them
select:
- platform: ld2412
ld2412_id: ld2412_dev
out_pin_level:
name: "Out Pin Level"
@@ -28,6 +28,11 @@ The gate threshold tests (footer_in_payload, truncated_answer, requery_after_giv
answers that hold the frame footer or are truncated, writes that fill the gates without a
number from the module's values, and writes held back until the module reports them.
test_uart_mock_ld2412_out_pin_level checks that the output pin level select sends the basic
configuration command with the gates and timeout the module reported when no number is configured.
test_uart_mock_ld2412_out_pin_level_held_back checks that the select holds that write back and asks
again while the module has not reported, then sends it with the reported values.
test_uart_mock_ld2412_selects checks that the distance resolution and baud rate selects show the
module's values and send the device code of the chosen option, that the name taking setters map a
name the same way, and that an unknown name sends nothing at all.
@@ -68,6 +73,9 @@ STILL_COMMAND = "10:00:04:00"
MOTION_QUERY = "02:00:13:00"
STILL_QUERY = "02:00:14:00"
CONFIG_MODE_LEFT = "02:00:FE:00"
BASIC_CONFIG = "07:00:02:00"
BASIC_CONFIG_QUERY = "02:00:12:00"
BASIC_CONFIG_HELD_BACK = "Command 02 held back"
# Set distance resolution (6 payload bytes) and set baud rate (2 payload bytes)
SET_RESOLUTION = "08:00:01:00"
SET_BAUD_RATE = "04:00:A1:00"
@@ -698,6 +706,80 @@ async def test_uart_mock_ld2412_requery_after_giveup(
assert all(value == pytest.approx(30.0) for value in gate_5_values)
@pytest.mark.asyncio
async def test_uart_mock_ld2412_out_pin_level(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test that the output pin level select resends the module's own gates and timeout."""
# Gates 3 to 9 and the 10 second timeout as the module reported them, with the pin level set to high
expected_payload = "03:09:0A:00:00"
events = _LogEvents()
async with (
run_compiled(yaml_config, line_callback=events.on_line),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
sub = await subscribe_after_initial_states(client, entities)
select = require_entity(entities, "out_pin_level", SelectInfo)
assert sub.initial_states[select.key].state == "low"
written = events.next_line_with(expected_payload)
client.select_command(select.key, "high")
await wait_or_fail(
written,
5.0,
"Timeout waiting for the basic configuration command",
expected_payload=expected_payload,
logged=events.lines,
)
# Exactly one basic configuration write, and not one built from the defaults
assert events.count(BASIC_CONFIG) == 1
assert events.count("01:0E:05:00") == 0
@pytest.mark.asyncio
async def test_uart_mock_ld2412_out_pin_level_held_back(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test that the output pin level select waits for the module's settings before it writes."""
# Gates 3 to 9 and the 10 second timeout as the module reports them, with the pin level set to high
expected_payload = "03:09:0A:00:00"
events = _LogEvents(BASIC_CONFIG_HELD_BACK)
async with (
run_compiled(yaml_config, line_callback=events.on_line),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
await subscribe_after_initial_states(client, entities)
select = require_entity(entities, "out_pin_level", SelectInfo)
# The boot query went unanswered, so nothing was written and the select has no state yet
assert events.count(BASIC_CONFIG) == 0
held_back = events.next_line_with(BASIC_CONFIG_HELD_BACK)
written = events.next_line_with(expected_payload)
client.select_command(select.key, "high")
await wait_or_fail(
asyncio.gather(held_back, written),
5.0,
"Timeout waiting for the held back basic configuration command",
expected_payload=expected_payload,
logged=events.lines,
)
# The write was held back once, asked again, and went out only after the answer
assert events.count(BASIC_CONFIG_QUERY) == 2
assert events.count(BASIC_CONFIG) == 1
assert events.index_after(-1, BASIC_CONFIG_HELD_BACK) < events.index_after(
-1, BASIC_CONFIG
)
assert events.count("01:0E:05:00") == 0
@pytest.mark.asyncio
async def test_uart_mock_ld2412_selects(
yaml_config: str,
+25 -4
View File
@@ -222,9 +222,15 @@ def test_api_client_does_not_import_heavy_modules() -> None:
# Packages a domain needs for its own schema; cheap and never an integration
_ENTITY_COMPONENT_DEPENDENCIES = {
"infrared": ("esphome.components.ir_rf_base",),
"light": ("esphome.components.power_supply", "esphome.components.const.css_colors"),
"radio_frequency": ("esphome.components.ir_rf_base",),
}
# Entity domains that still import integrations at top level
_ENTITY_COMPONENT_KNOWN_LEAKS = {
"infrared": "imports remote_base and api at top level",
"media_player": "audio pulls in the esp32 chain",
"radio_frequency": "imports remote_base and api at top level",
}
@@ -242,11 +248,26 @@ def _entity_components() -> list[str]:
def test_entity_component_tables_match_discovery() -> None:
"""Catch stale table entries and a discovery that silently finds nothing."""
found = set(_entity_components())
stale = _ENTITY_COMPONENT_DEPENDENCIES.keys() - found
stale = (
_ENTITY_COMPONENT_KNOWN_LEAKS.keys() | _ENTITY_COMPONENT_DEPENDENCIES.keys()
) - found
assert not stale, f"not discovered as entity components: {sorted(stale)}"
@pytest.mark.parametrize("component", _entity_components())
@pytest.mark.parametrize(
"component",
[
pytest.param(
c,
marks=pytest.mark.xfail(
reason=_ENTITY_COMPONENT_KNOWN_LEAKS[c], strict=True
),
)
if c in _ENTITY_COMPONENT_KNOWN_LEAKS
else c
for c in _entity_components()
],
)
def test_entity_component_does_not_import_integrations(component: str) -> None:
"""An entity component must not drag in its optional integrations.