Add Alarm Control Panel (#4770)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Graham Brown 2023-06-15 02:34:39 +02:00 committed by GitHub
parent 0411d52420
commit 54474e5b33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1860 additions and 6 deletions

View file

@ -19,6 +19,7 @@ esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix
@ -277,6 +278,7 @@ esphome/components/tca9548a/* @andreashergert1984
esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11
esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter
esphome/components/tlc5947/* @rnauber

View file

@ -0,0 +1,165 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority
from esphome.const import (
CONF_ID,
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_CODE,
)
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11"]
IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered"
CONF_ON_CLEARED = "on_cleared"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
StateTrigger = alarm_control_panel_ns.class_(
"StateTrigger", automation.Trigger.template()
)
TriggeredTrigger = alarm_control_panel_ns.class_(
"TriggeredTrigger", automation.Trigger.template()
)
ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template()
)
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
}
),
}
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
cv.Optional(CONF_CODE): cv.templatable(cv.string),
}
)
ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
}
)
async def setup_alarm_control_panel_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_alarm_control_panel(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_alarm_control_panel(var))
await setup_alarm_control_panel_core_(var, config)
@automation.register_action(
"alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.pending", PendingAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_pending_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
@automation.register_action(
"alarm_control_panel.triggered", TriggeredAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
@automation.register_condition(
"alarm_control_panel.is_armed",
AlarmControlPanelCondition,
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
)
async def alarm_control_panel_is_armed_to_code(
config, condition_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)
cg.add_define("USE_ALARM_CONTROL_PANEL")

View file

@ -0,0 +1,111 @@
#include <utility>
#include "alarm_control_panel.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); }
bool AlarmControlPanel::is_state_armed(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_ARMED_AWAY:
case ACP_STATE_ARMED_HOME:
case ACP_STATE_ARMED_NIGHT:
case ACP_STATE_ARMED_VACATION:
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return true;
default:
return false;
}
};
void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->last_update_ = millis();
if (state != this->current_state_) {
auto prev_state = this->current_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();
if (state == ACP_STATE_TRIGGERED) {
this->triggered_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);
}
}
}
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
this->triggered_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback));
}
void AlarmControlPanel::arm_away(optional<std::string> code) {
auto call = this->make_call();
call.arm_away();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_home(optional<std::string> code) {
auto call = this->make_call();
call.arm_home();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_night(optional<std::string> code) {
auto call = this->make_call();
call.arm_night();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_vacation(optional<std::string> code) {
auto call = this->make_call();
call.arm_vacation();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_custom_bypass(optional<std::string> code) {
auto call = this->make_call();
call.arm_custom_bypass();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::disarm(optional<std::string> code) {
auto call = this->make_call();
call.disarm();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,136 @@
#pragma once
#include <map>
#include "alarm_control_panel_call.h"
#include "alarm_control_panel_state.h"
#include "esphome/core/automation.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
enum AlarmControlPanelFeature : uint8_t {
// Matches Home Assistant values
ACP_FEAT_ARM_HOME = 1 << 0,
ACP_FEAT_ARM_AWAY = 1 << 1,
ACP_FEAT_ARM_NIGHT = 1 << 2,
ACP_FEAT_TRIGGER = 1 << 3,
ACP_FEAT_ARM_CUSTOM_BYPASS = 1 << 4,
ACP_FEAT_ARM_VACATION = 1 << 5,
};
class AlarmControlPanel : public EntityBase {
public:
/** Make a AlarmControlPanelCall
*
*/
AlarmControlPanelCall make_call();
/** Set the state of the alarm_control_panel.
*
* @param state The AlarmControlPanelState.
*/
void publish_state(AlarmControlPanelState state);
/** Add a callback for when the state of the alarm_control_panel changes
*
* @param callback The callback function
*/
void add_on_state_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to triggered
*
* @param callback The callback function
*/
void add_on_triggered_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);
/** A numeric representation of the supported features as per HomeAssistant
*
*/
virtual uint32_t get_supported_features() const = 0;
/** Returns if the alarm_control_panel has a code
*
*/
virtual bool get_requires_code() const = 0;
/** Returns if the alarm_control_panel requires a code to arm
*
*/
virtual bool get_requires_code_to_arm() const = 0;
/** arm the alarm in away mode
*
* @param code The code
*/
void arm_away(optional<std::string> code = nullopt);
/** arm the alarm in home mode
*
* @param code The code
*/
void arm_home(optional<std::string> code = nullopt);
/** arm the alarm in night mode
*
* @param code The code
*/
void arm_night(optional<std::string> code = nullopt);
/** arm the alarm in vacation mode
*
* @param code The code
*/
void arm_vacation(optional<std::string> code = nullopt);
/** arm the alarm in custom bypass mode
*
* @param code The code
*/
void arm_custom_bypass(optional<std::string> code = nullopt);
/** disarm the alarm
*
* @param code The code
*/
void disarm(optional<std::string> code = nullopt);
/** Get the state
*
*/
AlarmControlPanelState get_state() const { return this->current_state_; }
// is the state one of the armed states
bool is_state_armed(AlarmControlPanelState state);
protected:
friend AlarmControlPanelCall;
// in order to store last panel state in flash
ESPPreferenceObject pref_;
// current state
AlarmControlPanelState current_state_;
// the desired (or previous) state
AlarmControlPanelState desired_state_;
// last time the state was updated
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_{};
// clear callback
CallbackManager<void()> cleared_callback_{};
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,99 @@
#include "alarm_control_panel_call.h"
#include "alarm_control_panel.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
AlarmControlPanelCall &AlarmControlPanelCall::set_code(const std::string &code) {
this->code_ = code;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_away() {
this->state_ = ACP_STATE_ARMED_AWAY;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_home() {
this->state_ = ACP_STATE_ARMED_HOME;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_night() {
this->state_ = ACP_STATE_ARMED_NIGHT;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_vacation() {
this->state_ = ACP_STATE_ARMED_VACATION;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_custom_bypass() {
this->state_ = ACP_STATE_ARMED_CUSTOM_BYPASS;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::disarm() {
this->state_ = ACP_STATE_DISARMED;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::pending() {
this->state_ = ACP_STATE_PENDING;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::triggered() {
this->state_ = ACP_STATE_TRIGGERED;
return *this;
}
const optional<AlarmControlPanelState> &AlarmControlPanelCall::get_state() const { return this->state_; }
const optional<std::string> &AlarmControlPanelCall::get_code() const { return this->code_; }
void AlarmControlPanelCall::validate_() {
if (this->state_.has_value()) {
auto state = *this->state_;
if (this->parent_->is_state_armed(state) && this->parent_->get_state() != ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot arm when not disarmed");
this->state_.reset();
return;
}
if (state == ACP_STATE_PENDING && this->parent_->get_state() == ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot trip alarm when disarmed");
this->state_.reset();
return;
}
if (state == ACP_STATE_DISARMED &&
!(this->parent_->is_state_armed(this->parent_->get_state()) ||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
ESP_LOGW(TAG, "Cannot disarm when not armed");
this->state_.reset();
return;
}
if (state == ACP_STATE_ARMED_HOME && (this->parent_->get_supported_features() & ACP_FEAT_ARM_HOME) == 0) {
ESP_LOGW(TAG, "Cannot arm home when not supported");
this->state_.reset();
return;
}
}
}
void AlarmControlPanelCall::perform() {
this->validate_();
if (this->state_) {
this->parent_->control(*this);
}
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include "alarm_control_panel_state.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace alarm_control_panel {
class AlarmControlPanel;
class AlarmControlPanelCall {
public:
AlarmControlPanelCall(AlarmControlPanel *parent);
AlarmControlPanelCall &set_code(const std::string &code);
AlarmControlPanelCall &arm_away();
AlarmControlPanelCall &arm_home();
AlarmControlPanelCall &arm_night();
AlarmControlPanelCall &arm_vacation();
AlarmControlPanelCall &arm_custom_bypass();
AlarmControlPanelCall &disarm();
AlarmControlPanelCall &pending();
AlarmControlPanelCall &triggered();
void perform();
const optional<AlarmControlPanelState> &get_state() const;
const optional<std::string> &get_code() const;
protected:
AlarmControlPanel *parent_;
optional<std::string> code_{};
optional<AlarmControlPanelState> state_{};
void validate_();
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,34 @@
#include "alarm_control_panel_state.h"
namespace esphome {
namespace alarm_control_panel {
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_DISARMED:
return LOG_STR("DISARMED");
case ACP_STATE_ARMED_HOME:
return LOG_STR("ARMED_HOME");
case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT");
case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return LOG_STR("ARMED_CUSTOM_BYPASS");
case ACP_STATE_PENDING:
return LOG_STR("PENDING");
case ACP_STATE_ARMING:
return LOG_STR("ARMING");
case ACP_STATE_DISARMING:
return LOG_STR("DISARMING");
case ACP_STATE_TRIGGERED:
return LOG_STR("TRIGGERED");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
enum AlarmControlPanelState : uint8_t {
ACP_STATE_DISARMED = 0,
ACP_STATE_ARMED_HOME = 1,
ACP_STATE_ARMED_AWAY = 2,
ACP_STATE_ARMED_NIGHT = 3,
ACP_STATE_ARMED_VACATION = 4,
ACP_STATE_ARMED_CUSTOM_BYPASS = 5,
ACP_STATE_PENDING = 6,
ACP_STATE_ARMING = 7,
ACP_STATE_DISARMING = 8,
ACP_STATE_TRIGGERED = 9
};
/** Returns a string representation of the state.
*
* @param state The AlarmControlPanelState.
*/
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,115 @@
#pragma once
#include "esphome/core/automation.h"
#include "alarm_control_panel.h"
namespace esphome {
namespace alarm_control_panel {
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
}
};
class TriggeredTrigger : public Trigger<> {
public:
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
}
};
class ClearedTrigger : public Trigger<> {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
}
};
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
public:
explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_away();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
public:
explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_home();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction : public Action<Ts...> {
public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class PendingAction : public Action<Ts...> {
public:
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class TriggeredAction : public Action<Ts...> {
public:
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
public:
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
bool check(Ts... x) override {
return this->parent_->is_state_armed(this->parent_->get_state()) ||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
}
protected:
AlarmControlPanel *parent_;
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -56,6 +56,8 @@ service APIConnection {
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
}
@ -1454,3 +1456,63 @@ message VoiceAssistantEventResponse {
VoiceAssistantEvent event_type = 1;
repeated VoiceAssistantEventData data = 2;
}
// ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState {
ALARM_STATE_DISARMED = 0;
ALARM_STATE_ARMED_HOME = 1;
ALARM_STATE_ARMED_AWAY = 2;
ALARM_STATE_ARMED_NIGHT = 3;
ALARM_STATE_ARMED_VACATION = 4;
ALARM_STATE_ARMED_CUSTOM_BYPASS = 5;
ALARM_STATE_PENDING = 6;
ALARM_STATE_ARMING = 7;
ALARM_STATE_DISARMING = 8;
ALARM_STATE_TRIGGERED = 9;
}
enum AlarmControlPanelStateCommand {
ALARM_CONTROL_PANEL_DISARM = 0;
ALARM_CONTROL_PANEL_ARM_AWAY = 1;
ALARM_CONTROL_PANEL_ARM_HOME = 2;
ALARM_CONTROL_PANEL_ARM_NIGHT = 3;
ALARM_CONTROL_PANEL_ARM_VACATION = 4;
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5;
ALARM_CONTROL_PANEL_TRIGGER = 6;
}
message ListEntitiesAlarmControlPanelResponse {
option (id) = 94;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 supported_features = 8;
bool requires_code = 9;
bool requires_code_to_arm = 10;
}
message AlarmControlPanelStateResponse {
option (id) = 95;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
fixed32 key = 1;
AlarmControlPanelState state = 2;
}
message AlarmControlPanelCommandRequest {
option (id) = 96;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
fixed32 key = 1;
AlarmControlPanelStateCommand command = 2;
string code = 3;
}

View file

@ -931,6 +931,64 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
if (!this->state_subscription_)
return false;
AlarmControlPanelStateResponse resp{};
resp.key = a_alarm_control_panel->get_object_id_hash();
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
return this->send_alarm_control_panel_state_response(resp);
}
bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
ListEntitiesAlarmControlPanelResponse msg;
msg.key = a_alarm_control_panel->get_object_id_hash();
msg.object_id = a_alarm_control_panel->get_object_id();
msg.name = a_alarm_control_panel->get_name();
msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
msg.icon = a_alarm_control_panel->get_icon();
msg.disabled_by_default = a_alarm_control_panel->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_alarm_control_panel->get_entity_category());
msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
return this->send_list_entities_alarm_control_panel_response(msg);
}
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
if (a_alarm_control_panel == nullptr)
return;
auto call = a_alarm_control_panel->make_call();
switch (msg.command) {
case enums::ALARM_CONTROL_PANEL_DISARM:
call.disarm();
break;
case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
call.arm_away();
break;
case enums::ALARM_CONTROL_PANEL_ARM_HOME:
call.arm_home();
break;
case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
call.arm_night();
break;
case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
call.arm_vacation();
break;
case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
call.arm_custom_bypass();
break;
case enums::ALARM_CONTROL_PANEL_TRIGGER:
call.pending();
break;
}
call.set_code(msg.code);
call.perform();
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level)
return false;

View file

@ -129,6 +129,12 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
// we initiated ping

View file

@ -433,6 +433,57 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
switch (value) {
case enums::ALARM_STATE_DISARMED:
return "ALARM_STATE_DISARMED";
case enums::ALARM_STATE_ARMED_HOME:
return "ALARM_STATE_ARMED_HOME";
case enums::ALARM_STATE_ARMED_AWAY:
return "ALARM_STATE_ARMED_AWAY";
case enums::ALARM_STATE_ARMED_NIGHT:
return "ALARM_STATE_ARMED_NIGHT";
case enums::ALARM_STATE_ARMED_VACATION:
return "ALARM_STATE_ARMED_VACATION";
case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
case enums::ALARM_STATE_PENDING:
return "ALARM_STATE_PENDING";
case enums::ALARM_STATE_ARMING:
return "ALARM_STATE_ARMING";
case enums::ALARM_STATE_DISARMING:
return "ALARM_STATE_DISARMING";
case enums::ALARM_STATE_TRIGGERED:
return "ALARM_STATE_TRIGGERED";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
switch (value) {
case enums::ALARM_CONTROL_PANEL_DISARM:
return "ALARM_CONTROL_PANEL_DISARM";
case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
return "ALARM_CONTROL_PANEL_ARM_AWAY";
case enums::ALARM_CONTROL_PANEL_ARM_HOME:
return "ALARM_CONTROL_PANEL_ARM_HOME";
case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
return "ALARM_CONTROL_PANEL_ARM_NIGHT";
case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
return "ALARM_CONTROL_PANEL_ARM_VACATION";
case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
case enums::ALARM_CONTROL_PANEL_TRIGGER:
return "ALARM_CONTROL_PANEL_TRIGGER";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@ -6436,6 +6487,217 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->supported_features = value.as_uint32();
return true;
}
case 9: {
this->requires_code = value.as_bool();
return true;
}
case 10: {
this->requires_code_to_arm = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesAlarmControlPanelResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->supported_features);
buffer.encode_bool(9, this->requires_code);
buffer.encode_bool(10, this->requires_code_to_arm);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesAlarmControlPanelResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" supported_features: ");
sprintf(buffer, "%u", this->supported_features);
out.append(buffer);
out.append("\n");
out.append(" requires_code: ");
out.append(YESNO(this->requires_code));
out.append("\n");
out.append(" requires_code_to_arm: ");
out.append(YESNO(this->requires_code_to_arm));
out.append("\n");
out.append("}");
}
#endif
bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_enum<enums::AlarmControlPanelState>();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::AlarmControlPanelState>(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::AlarmControlPanelState>(this->state));
out.append("\n");
out.append("}");
}
#endif
bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->command = value.as_enum<enums::AlarmControlPanelStateCommand>();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->code = value.as_string();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::AlarmControlPanelStateCommand>(2, this->command);
buffer.encode_string(3, this->code);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" command: ");
out.append(proto_enum_to_string<enums::AlarmControlPanelStateCommand>(this->command));
out.append("\n");
out.append(" code: ");
out.append("'").append(this->code).append("'");
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View file

@ -176,6 +176,27 @@ enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_TTS_START = 7,
VOICE_ASSISTANT_TTS_END = 8,
};
enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0,
ALARM_STATE_ARMED_HOME = 1,
ALARM_STATE_ARMED_AWAY = 2,
ALARM_STATE_ARMED_NIGHT = 3,
ALARM_STATE_ARMED_VACATION = 4,
ALARM_STATE_ARMED_CUSTOM_BYPASS = 5,
ALARM_STATE_PENDING = 6,
ALARM_STATE_ARMING = 7,
ALARM_STATE_DISARMING = 8,
ALARM_STATE_TRIGGERED = 9,
};
enum AlarmControlPanelStateCommand : uint32_t {
ALARM_CONTROL_PANEL_DISARM = 0,
ALARM_CONTROL_PANEL_ARM_AWAY = 1,
ALARM_CONTROL_PANEL_ARM_HOME = 2,
ALARM_CONTROL_PANEL_ARM_NIGHT = 3,
ALARM_CONTROL_PANEL_ARM_VACATION = 4,
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
ALARM_CONTROL_PANEL_TRIGGER = 6,
};
} // namespace enums
@ -1680,6 +1701,56 @@ class VoiceAssistantEventResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
uint32_t supported_features{0};
bool requires_code{false};
bool requires_code_to_arm{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class AlarmControlPanelStateResponse : public ProtoMessage {
public:
uint32_t key{0};
enums::AlarmControlPanelState state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class AlarmControlPanelCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
enums::AlarmControlPanelStateCommand command{};
std::string code{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api
} // namespace esphome

View file

@ -476,6 +476,25 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_alarm_control_panel_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesAlarmControlPanelResponse>(msg, 94);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_alarm_control_panel_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<AlarmControlPanelStateResponse>(msg, 95);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
@ -883,6 +902,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_event_response(msg);
#endif
break;
}
case 96: {
#ifdef USE_ALARM_CONTROL_PANEL
AlarmControlPanelCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
#endif
this->on_alarm_control_panel_command_request(msg);
#endif
break;
}
@ -1295,6 +1325,19 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
this->subscribe_voice_assistant(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->alarm_control_panel_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View file

@ -239,6 +239,15 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -324,6 +333,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@ -405,6 +417,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif
};
} // namespace api

View file

@ -338,5 +338,14 @@ void APIServer::stop_voice_assistant() {
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
#endif
} // namespace api
} // namespace esphome

View file

@ -85,6 +85,10 @@ class APIServer : public Component, public Controller {
void stop_voice_assistant();
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
bool is_connected() const;
struct HomeAssistantStateSubscription {

View file

@ -69,6 +69,11 @@ bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_play
return this->client_->send_media_player_info(media_player);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
}
#endif
} // namespace api
} // namespace esphome

View file

@ -54,6 +54,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
bool on_end() override;

View file

@ -55,6 +55,11 @@ bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_play
return this->client_->send_media_player_state(media_player);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
}
#endif
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api

View file

@ -51,6 +51,9 @@ class InitialStateIterator : public ComponentIterator {
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
protected:
APIConnection *client_;

View file

@ -0,0 +1,123 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import (
binary_sensor,
alarm_control_panel,
)
from esphome.const import (
CONF_ID,
CONF_BINARY_SENSORS,
CONF_INPUT,
CONF_RESTORE_MODE,
)
from .. import template_ns
CODEOWNERS = ["@grahambrown11"]
CONF_CODES = "codes"
CONF_BYPASS_ARMED_HOME = "bypass_armed_home"
CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm"
CONF_ARMING_HOME_TIME = "arming_home_time"
CONF_ARMING_AWAY_TIME = "arming_away_time"
CONF_PENDING_TIME = "pending_time"
CONF_TRIGGER_TIME = "trigger_time"
FLAG_NORMAL = "normal"
FLAG_BYPASS_ARMED_HOME = "bypass_armed_home"
BinarySensorFlags = {
FLAG_NORMAL: 1 << 0,
FLAG_BYPASS_ARMED_HOME: 1 << 1,
}
TemplateAlarmControlPanel = template_ns.class_(
"TemplateAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component
)
TemplateAlarmControlPanelRestoreMode = template_ns.enum(
"TemplateAlarmControlPanelRestoreMode"
)
RESTORE_MODES = {
"ALWAYS_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_ALWAYS_DISARMED,
"RESTORE_DEFAULT_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED,
}
def validate_config(config):
if config.get(CONF_REQUIRES_CODE_TO_ARM, False) and not config.get(CONF_CODES, []):
raise cv.Invalid(
f"{CONF_REQUIRES_CODE_TO_ARM} cannot be True when there are no codes."
)
return config
TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA = cv.maybe_simple_value(
{
cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean,
},
key=CONF_INPUT,
)
TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA = (
alarm_control_panel.ALARM_CONTROL_PANEL_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TemplateAlarmControlPanel),
cv.Optional(CONF_CODES): cv.ensure_list(cv.string_strict),
cv.Optional(CONF_REQUIRES_CODE_TO_ARM): cv.boolean,
cv.Optional(CONF_ARMING_HOME_TIME): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_ARMING_AWAY_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_PENDING_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_TRIGGER_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA
),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_DISARMED"): cv.enum(
RESTORE_MODES, upper=True
),
}
).extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.All(
TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA,
validate_config,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await alarm_control_panel.register_alarm_control_panel(var, config)
if CONF_CODES in config:
for acode in config[CONF_CODES]:
cg.add(var.add_code(acode))
if CONF_REQUIRES_CODE_TO_ARM in config:
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))
cg.add(var.set_arming_away_time(config[CONF_ARMING_AWAY_TIME]))
cg.add(var.set_pending_time(config[CONF_PENDING_TIME]))
cg.add(var.set_trigger_time(config[CONF_TRIGGER_TIME]))
supports_arm_home = False
if CONF_ARMING_HOME_TIME in config:
cg.add(var.set_arming_home_time(config[CONF_ARMING_HOME_TIME]))
supports_arm_home = True
for sensor in config.get(CONF_BINARY_SENSORS, []):
bs = await cg.get_variable(sensor[CONF_INPUT])
flags = BinarySensorFlags[FLAG_NORMAL]
if sensor[CONF_BYPASS_ARMED_HOME]:
flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME]
supports_arm_home = True
cg.add(var.add_sensor(bs, flags))
cg.add(var.set_supports_arm_home(supports_arm_home))
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))

View file

@ -0,0 +1,180 @@
#include "template_alarm_control_panel.h"
#include <utility>
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace template_ {
using namespace esphome::alarm_control_panel;
static const char *const TAG = "template.alarm_control_panel";
TemplateAlarmControlPanel::TemplateAlarmControlPanel(){};
#ifdef USE_BINARY_SENSOR
void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags) {
this->sensor_map_[sensor] = flags;
};
#endif
void TemplateAlarmControlPanel::dump_config() {
ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:");
ESP_LOGCONFIG(TAG, " Current State: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)));
ESP_LOGCONFIG(TAG, " Number of Codes: %u", this->codes_.size());
if (!this->codes_.empty())
ESP_LOGCONFIG(TAG, " Requires Code To Arm: %s", YESNO(this->requires_code_to_arm_));
ESP_LOGCONFIG(TAG, " Arming Away Time: %us", (this->arming_away_time_ / 1000));
if (this->arming_home_time_ != 0)
ESP_LOGCONFIG(TAG, " Arming Home Time: %us", (this->arming_home_time_ / 1000));
ESP_LOGCONFIG(TAG, " Pending Time: %us", (this->pending_time_ / 1000));
ESP_LOGCONFIG(TAG, " Trigger Time: %us", (this->trigger_time_ / 1000));
ESP_LOGCONFIG(TAG, " Supported Features: %u", this->get_supported_features());
#ifdef USE_BINARY_SENSOR
for (auto sensor_pair : this->sensor_map_) {
ESP_LOGCONFIG(TAG, " Binary Sesnsor:");
ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str());
ESP_LOGCONFIG(TAG, " Armed home bypass: %s",
TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME));
}
#endif
}
void TemplateAlarmControlPanel::setup() {
ESP_LOGCONFIG(TAG, "Setting up Template AlarmControlPanel '%s'...", this->name_.c_str());
switch (this->restore_mode_) {
case ALARM_CONTROL_PANEL_ALWAYS_DISARMED:
this->current_state_ = ACP_STATE_DISARMED;
break;
case ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED: {
uint8_t value;
this->pref_ = global_preferences->make_preference<uint8_t>(this->get_object_id_hash());
if (this->pref_.load(&value)) {
this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value);
} else {
this->current_state_ = ACP_STATE_DISARMED;
}
break;
}
}
this->desired_state_ = this->current_state_;
}
void TemplateAlarmControlPanel::loop() {
// change from ARMING to ARMED_x after the arming_time_ has passed
if (this->current_state_ == ACP_STATE_ARMING) {
auto delay = this->arming_away_time_;
if (this->desired_state_ == ACP_STATE_ARMED_HOME) {
delay = this->arming_home_time_;
}
if ((millis() - this->last_update_) > delay) {
this->publish_state(this->desired_state_);
}
return;
}
// change from PENDING to TRIGGERED after the delay_time_ has passed
if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
this->publish_state(ACP_STATE_TRIGGERED);
return;
}
auto future_state = this->current_state_;
// reset triggered if all clear
if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
(millis() - this->last_update_) > this->trigger_time_) {
future_state = this->desired_state_;
}
bool trigger = false;
#ifdef USE_BINARY_SENSOR
if (this->is_state_armed(future_state)) {
// TODO might be better to register change for each sensor in setup...
for (auto sensor_pair : this->sensor_map_) {
if (sensor_pair.first->state) {
if (this->current_state_ == ACP_STATE_ARMED_HOME &&
(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
continue;
}
trigger = true;
break;
}
}
}
#endif
if (trigger) {
if (this->pending_time_ > 0 && this->current_state_ != ACP_STATE_TRIGGERED) {
this->publish_state(ACP_STATE_PENDING);
} else {
this->publish_state(ACP_STATE_TRIGGERED);
}
} else if (future_state != this->current_state_) {
this->publish_state(future_state);
}
}
bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
if (!this->codes_.empty()) {
if (code.has_value()) {
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
}
ESP_LOGD(TAG, "No code provided");
return false;
}
return true;
}
uint32_t TemplateAlarmControlPanel::get_supported_features() const {
uint32_t features = ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER;
if (this->supports_arm_home_) {
features |= ACP_FEAT_ARM_HOME;
}
return features;
}
bool TemplateAlarmControlPanel::get_requires_code() const { return !this->codes_.empty(); }
void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_panel::AlarmControlPanelState state,
uint32_t delay) {
if (this->current_state_ != ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot arm when not disarmed");
return;
}
if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
ESP_LOGW(TAG, "Not arming code doesn't match");
return;
}
this->desired_state_ = state;
if (delay > 0) {
this->publish_state(ACP_STATE_ARMING);
} else {
this->publish_state(state);
}
}
void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) {
if (call.get_state()) {
if (call.get_state() == ACP_STATE_ARMED_AWAY) {
this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
} else if (call.get_state() == ACP_STATE_ARMED_HOME) {
this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
} else if (call.get_state() == ACP_STATE_DISARMED) {
if (!this->is_code_valid_(call.get_code())) {
ESP_LOGW(TAG, "Not disarming code doesn't match");
return;
}
this->desired_state_ = ACP_STATE_DISARMED;
this->publish_state(ACP_STATE_DISARMED);
} else if (call.get_state() == ACP_STATE_TRIGGERED) {
this->publish_state(ACP_STATE_TRIGGERED);
} else if (call.get_state() == ACP_STATE_PENDING) {
this->publish_state(ACP_STATE_PENDING);
} else {
ESP_LOGE(TAG, "State not yet implemented: %s",
LOG_STR_ARG(alarm_control_panel_state_to_string(*call.get_state())));
}
}
}
} // namespace template_
} // namespace esphome

View file

@ -0,0 +1,116 @@
#pragma once
#include <map>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
namespace esphome {
namespace template_ {
#ifdef USE_BINARY_SENSOR
enum BinarySensorFlags : uint16_t {
BINARY_SENSOR_MODE_NORMAL = 1 << 0,
BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1,
};
#endif
enum TemplateAlarmControlPanelRestoreMode {
ALARM_CONTROL_PANEL_ALWAYS_DISARMED,
ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED,
};
class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component {
public:
TemplateAlarmControlPanel();
void dump_config() override;
void setup() override;
void loop() override;
uint32_t get_supported_features() const override;
bool get_requires_code() const override;
bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; }
void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
#ifdef USE_BINARY_SENSOR
/** Add a binary_sensor to the alarm_panel.
*
* @param sensor The BinarySensor instance.
* @param ignore_when_home if this should be ignored when armed_home mode
*/
void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0);
#endif
/** add a code
*
* @param code The code
*/
void add_code(const std::string &code) { this->codes_.push_back(code); }
/** set requires a code to arm
*
* @param code_to_arm The requires code to arm
*/
void set_requires_code_to_arm(bool code_to_arm) { this->requires_code_to_arm_ = code_to_arm; }
/** set the delay before arming away
*
* @param time The milliseconds
*/
void set_arming_away_time(uint32_t time) { this->arming_away_time_ = time; }
/** set the delay before arming home
*
* @param time The milliseconds
*/
void set_arming_home_time(uint32_t time) { this->arming_home_time_ = time; }
/** set the delay before triggering
*
* @param time The milliseconds
*/
void set_pending_time(uint32_t time) { this->pending_time_ = time; }
/** set the delay before resetting after triggered
*
* @param time The milliseconds
*/
void set_trigger_time(uint32_t time) { this->trigger_time_ = time; }
void set_supports_arm_home(bool supports_arm_home) { supports_arm_home_ = supports_arm_home; }
protected:
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
#ifdef USE_BINARY_SENSOR
// the map of binary sensors that the alarm_panel monitors with their modes
std::map<binary_sensor::BinarySensor *, uint16_t> sensor_map_;
#endif
TemplateAlarmControlPanelRestoreMode restore_mode_{};
// the arming away delay
uint32_t arming_away_time_;
// the arming home delay
uint32_t arming_home_time_{0};
// the trigger delay
uint32_t pending_time_;
// the time in trigger
uint32_t trigger_time_;
// a list of codes
std::vector<std::string> codes_;
// requires a code to arm
bool requires_code_to_arm_ = false;
bool supports_arm_home_ = false;
// check if the code is valid
bool is_code_valid_(optional<std::string> code);
void arm_(optional<std::string> code, alarm_control_panel::AlarmControlPanelState state, uint32_t delay);
};
} // namespace template_
} // namespace esphome

View file

@ -91,6 +91,16 @@ bool ListEntitiesIterator::on_select(select::Select *select) {
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->web_server_->events_.send(
this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL)
.c_str(),
"state");
return true;
}
#endif
} // namespace web_server
} // namespace esphome

View file

@ -49,6 +49,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
protected:
WebServer *web_server_;

View file

@ -813,6 +813,9 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
}
#endif
// Longest: HORIZONTAL
#define PSTR_LOCAL(mode_s) strncpy_P(buf, (PGM_P) ((mode_s)), 15)
#ifdef USE_CLIMATE
void WebServer::on_climate_update(climate::Climate *obj) {
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
@ -869,16 +872,13 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
request->send(404);
}
// Longest: HORIZONTAL
#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P) ((mode_s)), 15)
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits();
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
char __buf[16];
char buf[16];
if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("modes");
@ -996,6 +996,34 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
}
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value,
JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
char buf[16];
set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
});
}
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
}
}
request->send(404);
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/")
return true;
@ -1073,6 +1101,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (request->method() == HTTP_GET && match.domain == "alarm_control_panel")
return true;
#endif
return false;
}
void WebServer::handleRequest(AsyncWebServerRequest *request) {
@ -1180,6 +1213,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return;
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (match.domain == "alarm_control_panel") {
this->handle_alarm_control_panel_request(request, match);
return;
}
#endif
}
bool WebServer::isRequestHandlerTrivial() { return false; }

View file

@ -216,6 +216,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
/// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the alarm_control_panel state with its value as a JSON string.
std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method.

View file

@ -48,6 +48,9 @@
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h"
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
namespace esphome {
@ -126,6 +129,12 @@ class Application {
void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); }
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->alarm_control_panels_.push_back(a_alarm_control_panel);
}
#endif
/// Register the component in this Application instance.
template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@ -296,6 +305,18 @@ class Application {
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
const std::vector<alarm_control_panel::AlarmControlPanel *> &get_alarm_control_panels() {
return this->alarm_control_panels_;
}
alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->alarm_control_panels_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler;
protected:
@ -349,6 +370,9 @@ class Application {
#ifdef USE_MEDIA_PLAYER
std::vector<media_player::MediaPlayer *> media_players_{};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
std::vector<alarm_control_panel::AlarmControlPanel *> alarm_control_panels_{};
#endif
std::string name_;
std::string friendly_name_;

View file

@ -246,6 +246,21 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
case IteratorState::ALARM_CONTROL_PANEL:
if (this->at_ >= App.get_alarm_control_panels().size()) {
advance_platform = true;
} else {
auto *a_alarm_control_panel = App.get_alarm_control_panels()[this->at_];
if (a_alarm_control_panel->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_alarm_control_panel(a_alarm_control_panel);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View file

@ -65,6 +65,9 @@ class ComponentIterator {
#endif
#ifdef USE_MEDIA_PLAYER
virtual bool on_media_player(media_player::MediaPlayer *media_player);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0;
#endif
virtual bool on_end();
@ -116,6 +119,9 @@ class ComponentIterator {
#endif
#ifdef USE_MEDIA_PLAYER
MEDIA_PLAYER,
#endif
#ifdef USE_ALARM_CONTROL_PANEL
ALARM_CONTROL_PANEL,
#endif
MAX,
} state_{IteratorState::NONE};

View file

@ -79,6 +79,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_media_player_update(obj); });
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
for (auto *obj : App.get_alarm_control_panels()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); });
}
#endif
}
} // namespace esphome

View file

@ -40,6 +40,9 @@
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h"
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
namespace esphome {
@ -82,6 +85,9 @@ class Controller {
#ifdef USE_MEDIA_PLAYER
virtual void on_media_player_update(media_player::MediaPlayer *obj){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){};
#endif
};
} // namespace esphome

View file

@ -17,6 +17,7 @@
#define USE_API
#define USE_API_NOISE
#define USE_API_PLAINTEXT
#define USE_ALARM_CONTROL_PANEL
#define USE_BINARY_SENSOR
#define USE_BUTTON
#define USE_CLIMATE

View file

@ -3371,3 +3371,21 @@ lcd_menu:
on_prev:
then:
lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());'
alarm_control_panel:
- platform: template
id: alarmcontrolpanel1
name: Alarm Panel
codes:
- "1234"
requires_code_to_arm: true
arming_home_time: 1s
arming_away_time: 15s
pending_time: 15s
trigger_time: 30s
binary_sensors:
- binary_sensor1
on_state:
then:
- lambda: !lambda |-
ESP_LOGD("TEST", "State change %s", alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()));

View file

@ -822,7 +822,6 @@ switch:
name: R0 Switch
component_name: page0.r0
climate:
- platform: bang_bang
name: Bang Bang Climate
@ -1106,7 +1105,6 @@ rf_bridge:
- rf_bridge.send_raw:
raw: "AAA5070008001000ABC12355"
display:
- platform: nextion
uart_id: uart_1
@ -1171,3 +1169,22 @@ daly_bms:
qr_code:
- id: homepage_qr
value: https://esphome.io/index.html
alarm_control_panel:
- platform: template
id: alarmcontrolpanel1
name: Alarm Panel
codes:
- "1234"
requires_code_to_arm: true
arming_home_time: 1s
arming_away_time: 15s
pending_time: 15s
trigger_time: 30s
binary_sensors:
- input: bin1
bypass_armed_home: true
on_state:
then:
- lambda: !lambda |-
ESP_LOGD("TEST", "State change %s", alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()));