[alarm_control_panel] Replace callbacks with listener interface

This commit is contained in:
J. Nick Koston
2025-11-28 16:11:23 -06:00
parent 2e55296640
commit e8bc19a07d
6 changed files with 100 additions and 185 deletions

View File

@@ -35,29 +35,15 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
this->state_callback_.call();
for (auto *listener : this->listeners_) {
listener->on_state(state, prev_state);
}
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this);
#endif
if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
}
if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call();
}
if (state == this->desired_state_) {
// only store when in the desired state
this->pref_.save(&state);
@@ -65,48 +51,14 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
}
}
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
void AlarmControlPanel::notify_chime() {
for (auto *listener : this->listeners_)
listener->on_chime();
}
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
this->triggered_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_chime_callback(std::function<void()> &&callback) {
this->chime_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_ready_callback(std::function<void()> &&callback) {
this->ready_callback_.add(std::move(callback));
void AlarmControlPanel::notify_ready() {
for (auto *listener : this->listeners_)
listener->on_ready();
}
void AlarmControlPanel::arm_away(optional<std::string> code) {

View File

@@ -1,17 +1,32 @@
#pragma once
#include <map>
#include <vector>
#include "alarm_control_panel_call.h"
#include "alarm_control_panel_state.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace alarm_control_panel {
/// Listener interface for alarm control panel events.
/// Implement this interface and register with add_listener() to receive notifications.
class AlarmControlPanelListener {
public:
virtual ~AlarmControlPanelListener() = default;
/// Called when state changes. Check new_state to filter specific states.
virtual void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) {}
/// Called when a chime zone opens while disarmed.
virtual void on_chime() {}
/// Called when ready state changes.
virtual void on_ready() {}
};
enum AlarmControlPanelFeature : uint8_t {
// Matches Home Assistant values
ACP_FEAT_ARM_HOME = 1 << 0,
@@ -35,71 +50,19 @@ class AlarmControlPanel : public EntityBase {
*/
void publish_state(AlarmControlPanelState state);
/** Add a callback for when the state of the alarm_control_panel changes
/** Register a listener for alarm control panel events.
*
* @param callback The callback function
* @param listener The listener to add (must remain valid for lifetime of panel)
*/
void add_on_state_callback(std::function<void()> &&callback);
void add_listener(AlarmControlPanelListener *listener) { this->listeners_.push_back(listener); }
/** Add a callback for when the state of the alarm_control_panel chanes to triggered
*
* @param callback The callback function
/** Notify listeners of a chime event (zone opened while disarmed).
*/
void add_on_triggered_callback(std::function<void()> &&callback);
void notify_chime();
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
/** Notify listeners of a ready state change.
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered
*
* @param callback The callback function
*/
void add_on_cleared_callback(std::function<void()> &&callback);
/** Add a callback for when a chime zone goes from closed to open
*
* @param callback The callback function
*/
void add_on_chime_callback(std::function<void()> &&callback);
/** Add a callback for when a ready state changes
*
* @param callback The callback function
*/
void add_on_ready_callback(std::function<void()> &&callback);
void notify_ready();
/** A numeric representation of the supported features as per HomeAssistant
*
@@ -172,28 +135,8 @@ class AlarmControlPanel : public EntityBase {
uint32_t last_update_;
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback
CallbackManager<void()> state_callback_{};
// trigger callback
CallbackManager<void()> triggered_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback
CallbackManager<void()> cleared_callback_{};
// chime callback
CallbackManager<void()> chime_callback_{};
// ready callback
CallbackManager<void()> ready_callback_{};
// registered listeners
std::vector<AlarmControlPanelListener *> listeners_;
};
} // namespace alarm_control_panel

View File

@@ -6,81 +6,94 @@
namespace esphome {
namespace alarm_control_panel {
class StateTrigger : public Trigger<> {
class StateTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override { this->trigger(); }
};
class TriggeredTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_TRIGGERED)
this->trigger();
}
};
class TriggeredTrigger : public Trigger<> {
class ArmingTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_ARMING)
this->trigger();
}
};
class ArmingTrigger : public Trigger<> {
class PendingTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); });
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_PENDING)
this->trigger();
}
};
class PendingTrigger : public Trigger<> {
class ArmedHomeTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_ARMED_HOME)
this->trigger();
}
};
class ArmedHomeTrigger : public Trigger<> {
class ArmedNightTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_ARMED_NIGHT)
this->trigger();
}
};
class ArmedNightTrigger : public Trigger<> {
class ArmedAwayTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_ARMED_AWAY)
this->trigger();
}
};
class ArmedAwayTrigger : public Trigger<> {
class DisarmedTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (new_state == ACP_STATE_DISARMED)
this->trigger();
}
};
class DisarmedTrigger : public Trigger<> {
class ClearedTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_state(AlarmControlPanelState new_state, AlarmControlPanelState prev_state) override {
if (prev_state == ACP_STATE_TRIGGERED)
this->trigger();
}
};
class ClearedTrigger : public Trigger<> {
class ChimeTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
}
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_chime() override { this->trigger(); }
};
class ChimeTrigger : public Trigger<> {
class ReadyTrigger final : public Trigger<>, public AlarmControlPanelListener {
public:
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); });
}
};
class ReadyTrigger : public Trigger<> {
public:
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); });
}
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { alarm_control_panel->add_listener(this); }
void on_ready() override { this->trigger(); }
};
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {

View File

@@ -16,7 +16,7 @@ using namespace esphome::alarm_control_panel;
MQTTAlarmControlPanelComponent::MQTTAlarmControlPanelComponent(AlarmControlPanel *alarm_control_panel)
: alarm_control_panel_(alarm_control_panel) {}
void MQTTAlarmControlPanelComponent::setup() {
this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); });
this->alarm_control_panel_->add_listener(this);
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
auto call = this->alarm_control_panel_->make_call();
if (strcasecmp(payload.c_str(), "ARM_AWAY") == 0) {

View File

@@ -11,7 +11,8 @@
namespace esphome {
namespace mqtt {
class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent {
class MQTTAlarmControlPanelComponent final : public mqtt::MQTTComponent,
public alarm_control_panel::AlarmControlPanelListener {
public:
explicit MQTTAlarmControlPanelComponent(alarm_control_panel::AlarmControlPanel *alarm_control_panel);
@@ -25,6 +26,12 @@ class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent {
void dump_config() override;
// AlarmControlPanelListener interface
void on_state(alarm_control_panel::AlarmControlPanelState new_state,
alarm_control_panel::AlarmControlPanelState prev_state) override {
this->publish_state();
}
protected:
std::string component_type() const override;
const EntityBase *get_entity() const override;

View File

@@ -133,7 +133,7 @@ void TemplateAlarmControlPanel::loop() {
if ((!this->sensor_data_[info.store_index].last_chime_state) && (sensor->state)) {
// Must be disarmed to chime
if (this->current_state_ == ACP_STATE_DISARMED) {
this->chime_callback_.call();
this->notify_chime();
}
}
// Record the sensor state change
@@ -182,7 +182,7 @@ void TemplateAlarmControlPanel::loop() {
// Call the ready state change callback if there was a change
if (this->sensors_ready_ != sensors_ready) {
this->sensors_ready_ = sensors_ready;
this->ready_callback_.call();
this->notify_ready();
}
#endif