[template] Keep alarm codes in a shared flash table (#20084)

This commit is contained in:
J. Nick Koston
2026-10-09 09:19:35 -10:00
committed by GitHub
parent 94e6bd90d8
commit 588fde91b2
5 changed files with 66 additions and 17 deletions
@@ -117,8 +117,13 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = await alarm_control_panel.new_alarm_control_panel(config)
await cg.register_component(var, config)
if CONF_CODES in config:
cg.add(var.set_codes(config[CONF_CODES]))
# Shared flash table ended by nullptr, so the panel stores only a pointer.
# requires_code_to_arm needs codes (validate_config) and defaults to false.
if codes := config.get(CONF_CODES):
table = cg.shared_progmem_array(
"alarm_codes", cg.const_char_ptr, [*codes, cg.nullptr]
)
cg.add(var.set_codes(table))
if CONF_REQUIRES_CODE_TO_ARM in config:
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))
@@ -36,6 +36,11 @@ static const LogString *sensor_type_to_string(AlarmSensorType type) {
#endif
void TemplateAlarmControlPanel::dump_config() {
size_t num_codes = 0;
if (this->codes_ != nullptr) {
while (this->codes_[num_codes] != nullptr)
num_codes++;
}
ESP_LOGCONFIG(TAG,
"TemplateAlarmControlPanel:\n"
" Current State: %s\n"
@@ -47,8 +52,8 @@ void TemplateAlarmControlPanel::dump_config() {
" Pending Time: %" PRIu32 "s\n"
" Trigger Time: %" PRIu32 "s\n"
" Supported Features: %" PRIu32,
LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size(),
YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), num_codes,
YESNO(this->codes_ != nullptr && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
(this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
(this->trigger_time_ / 1000), this->get_supported_features());
#ifdef USE_BINARY_SENSOR
@@ -194,13 +199,12 @@ void TemplateAlarmControlPanel::loop() {
}
bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
if (!this->codes_.empty()) {
if (this->codes_ != nullptr) {
if (code.has_value()) {
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
// Use strcmp for const char* comparison
const char *code_cstr = code.value().c_str();
for (const char *stored_code : this->codes_) {
if (strcmp(stored_code, code_cstr) == 0)
for (const char *const *stored = this->codes_; *stored != nullptr; stored++) {
if (strcmp(*stored, code_cstr) == 0)
return true;
}
return false;
@@ -61,7 +61,7 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
void setup() override;
void loop() override;
uint32_t get_supported_features() const override;
bool get_requires_code() const override { return !this->codes_.empty(); }
bool get_requires_code() const override { return this->codes_ != nullptr; }
bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; }
bool get_all_sensors_ready() { return this->sensors_ready_; };
void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
@@ -83,14 +83,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED);
#endif
/** Set the codes (from initializer list).
/** Set the codes.
*
* @param codes The list of valid codes
* @param codes nullptr-terminated table of valid codes; must outlive the panel
*/
void set_codes(std::initializer_list<const char *> codes) { this->codes_ = codes; }
// Deleted overload to catch incorrect std::string usage at compile time
void set_codes(std::initializer_list<std::string> codes) = delete;
void set_codes(const char *const *codes) { this->codes_ = codes; }
/** set requires a code to arm
*
@@ -150,8 +147,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
uint32_t pending_time_;
// the time in trigger
uint32_t trigger_time_;
// a list of codes (const char* pointers to string literals in flash)
FixedVector<const char *> codes_;
// nullptr-terminated table of codes, shared in flash
const char *const *codes_{nullptr};
// requires a code to arm
bool requires_code_to_arm_ = false;
bool supports_arm_home_ = false;
@@ -0,0 +1,23 @@
esphome:
name: test
esp8266:
board: d1_mini
alarm_control_panel:
- platform: template
id: panel_a
name: Panel A
codes:
- "1234"
- "5678"
- platform: template
id: panel_b
name: Panel B
codes:
- "1234"
- "5678"
- platform: template
id: panel_none
name: Panel None
codes: []
@@ -0,0 +1,20 @@
"""Tests for the template alarm control panel codes codegen."""
from collections.abc import Callable
from pathlib import Path
def test_codes_share_one_progmem_table(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Identical code lists share one nullptr-terminated flash table; empty lists set none."""
main_cpp = generate_main(component_config_path("alarm_codes.yaml"))
assert (
'static constexpr const char * alarm_codes[] PROGMEM = {"1234", "5678", nullptr};'
in main_cpp
)
assert "panel_a->set_codes(alarm_codes);" in main_cpp
assert "panel_b->set_codes(alarm_codes);" in main_cpp
assert "panel_none->set_codes(" not in main_cpp