ESPHome 2026.2.4
Loading...
Searching...
No Matches
template_alarm_control_panel.cpp
Go to the documentation of this file.
1
3#include <utility>
7#include "esphome/core/log.h"
9
11
12using namespace esphome::alarm_control_panel;
13
14static const char *const TAG = "template.alarm_control_panel";
15
17
18#ifdef USE_BINARY_SENSOR
20 // Save the flags and type. Assign a store index for the per sensor data type.
22 sd.last_chime_state = false;
23 AlarmSensor alarm_sensor;
24 alarm_sensor.sensor = sensor;
25 alarm_sensor.info.flags = flags;
26 alarm_sensor.info.type = type;
27 alarm_sensor.info.store_index = this->next_store_index_++;
28 this->sensors_.push_back(alarm_sensor);
29 this->sensor_data_.push_back(sd);
30};
31
32// Alarm sensor type strings indexed by AlarmSensorType enum (0-3): DELAYED, INSTANT, DELAYED_FOLLOWER, INSTANT_ALWAYS
33PROGMEM_STRING_TABLE(AlarmSensorTypeStrings, "delayed", "instant", "delayed_follower", "instant_always");
34
35static const LogString *sensor_type_to_string(AlarmSensorType type) {
36 return AlarmSensorTypeStrings::get_log_str(static_cast<uint8_t>(type), 0);
37}
38#endif
39
41 ESP_LOGCONFIG(TAG,
42 "TemplateAlarmControlPanel:\n"
43 " Current State: %s\n"
44 " Number of Codes: %zu\n"
45 " Requires Code To Arm: %s\n"
46 " Arming Away Time: %" PRIu32 "s\n"
47 " Arming Home Time: %" PRIu32 "s\n"
48 " Arming Night Time: %" PRIu32 "s\n"
49 " Pending Time: %" PRIu32 "s\n"
50 " Trigger Time: %" PRIu32 "s\n"
51 " Supported Features: %" PRIu32,
53 YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
54 (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
55 (this->trigger_time_ / 1000), this->get_supported_features());
56#ifdef USE_BINARY_SENSOR
57 for (const auto &alarm_sensor : this->sensors_) {
58 const uint16_t flags = alarm_sensor.info.flags;
59 ESP_LOGCONFIG(TAG,
60 " Binary Sensor:\n"
61 " Name: %s\n"
62 " Type: %s\n"
63 " Armed home bypass: %s\n"
64 " Armed night bypass: %s\n"
65 " Auto bypass: %s\n"
66 " Chime mode: %s",
67 alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
71 }
72#endif
73}
74
78 uint8_t value;
80 if (this->pref_.load(&value)) {
82 }
83 }
84 this->desired_state_ = this->current_state_;
85}
86
88 // change from ARMING to ARMED_x after the arming_time_ has passed
89 if (this->current_state_ == ACP_STATE_ARMING) {
90 auto delay = this->arming_away_time_;
93 }
96 }
97 if ((millis() - this->last_update_) > delay) {
99 this->publish_state(this->desired_state_);
100 }
101 return;
102 }
103 // change from PENDING to TRIGGERED after the delay_time_ has passed
104 if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
106 return;
107 }
108 auto next_state = this->current_state_;
109 // reset triggered if all clear
110 if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
111 (millis() - this->last_update_) > this->trigger_time_) {
112 next_state = this->desired_state_;
113 }
114
115 bool delayed_sensor_faulted = false;
116 bool instant_sensor_faulted = false;
117
118#ifdef USE_BINARY_SENSOR
119 // Test all of the sensors regardless of the alarm panel state
120 for (const auto &alarm_sensor : this->sensors_) {
121 const auto &info = alarm_sensor.info;
122 auto *sensor = alarm_sensor.sensor;
123 // Check for chime zones
124 if (info.flags & BINARY_SENSOR_MODE_CHIME) {
125 // Look for the transition from closed to open
126 if ((!this->sensor_data_[info.store_index].last_chime_state) && (sensor->state)) {
127 // Must be disarmed to chime
128 if (this->current_state_ == ACP_STATE_DISARMED) {
129 this->chime_callback_.call();
130 }
131 }
132 // Record the sensor state change
133 this->sensor_data_[info.store_index].last_chime_state = sensor->state;
134 }
135 // Check for faulted sensors
136 if (sensor->state) {
137 // Skip if auto bypassed
138 if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(),
139 info.store_index) == 1) {
140 continue;
141 }
142 // Skip if bypass armed home
144 continue;
145 }
146 // Skip if bypass armed night
148 continue;
149 }
150
151 switch (info.type) {
153 next_state = ACP_STATE_TRIGGERED;
154 [[fallthrough]];
156 instant_sensor_faulted = true;
157 break;
159 // Look to see if we are in the pending state
160 if (this->current_state_ == ACP_STATE_PENDING) {
161 delayed_sensor_faulted = true;
162 } else {
163 instant_sensor_faulted = true;
164 }
165 break;
167 default:
168 delayed_sensor_faulted = true;
169 }
170 }
171 }
172 // Update all sensors ready flag
173 bool sensors_ready = !(instant_sensor_faulted || delayed_sensor_faulted);
174
175 // Call the ready state change callback if there was a change
176 if (this->sensors_ready_ != sensors_ready) {
177 this->sensors_ready_ = sensors_ready;
178 this->ready_callback_.call();
179 }
180
181#endif
182 if (this->is_state_armed(next_state) && (!this->sensors_ready_)) {
183 // Instant sensors
184 if (instant_sensor_faulted) {
186 } else if (delayed_sensor_faulted) {
187 // Delayed sensors
188 if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) {
190 } else {
192 }
193 }
194 } else if (next_state != this->current_state_) {
195 this->publish_state(next_state);
196 }
197}
198
200 if (!this->codes_.empty()) {
201 if (code.has_value()) {
202 ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
203 // Use strcmp for const char* comparison
204 const char *code_cstr = code.value().c_str();
205 for (const char *stored_code : this->codes_) {
206 if (strcmp(stored_code, code_cstr) == 0)
207 return true;
208 }
209 return false;
210 }
211 ESP_LOGD(TAG, "No code provided");
212 return false;
213 }
214 return true;
215}
216
218 uint32_t features = ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER;
219 if (this->supports_arm_home_) {
220 features |= ACP_FEAT_ARM_HOME;
221 }
222 if (this->supports_arm_night_) {
223 features |= ACP_FEAT_ARM_NIGHT;
224 }
225 return features;
226}
227
229 uint32_t delay) {
230 if (this->current_state_ != ACP_STATE_DISARMED) {
231 ESP_LOGW(TAG, "Cannot arm when not disarmed");
232 return;
233 }
234 if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
235 ESP_LOGW(TAG, "Not arming code doesn't match");
236 return;
237 }
238 this->desired_state_ = state;
239 if (delay > 0) {
241 } else {
242 this->bypass_before_arming();
243 this->publish_state(state);
244 }
245}
246
248#ifdef USE_BINARY_SENSOR
249 for (const auto &alarm_sensor : this->sensors_) {
250 // Check for faulted bypass_auto sensors and remove them from monitoring
251 if ((alarm_sensor.info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (alarm_sensor.sensor->state)) {
252 ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", alarm_sensor.sensor->get_name().c_str());
253 this->bypassed_sensor_indicies_.push_back(alarm_sensor.info.store_index);
254 }
255 }
256#endif
257}
258
260 if (call.get_state()) {
261 if (call.get_state() == ACP_STATE_ARMED_AWAY) {
262 this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
263 } else if (call.get_state() == ACP_STATE_ARMED_HOME) {
264 this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
265 } else if (call.get_state() == ACP_STATE_ARMED_NIGHT) {
266 this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_);
267 } else if (call.get_state() == ACP_STATE_DISARMED) {
268 if (!this->is_code_valid_(call.get_code())) {
269 ESP_LOGW(TAG, "Not disarming code doesn't match");
270 return;
271 }
274#ifdef USE_BINARY_SENSOR
275 this->bypassed_sensor_indicies_.clear();
276#endif
277 } else if (call.get_state() == ACP_STATE_TRIGGERED) {
279 } else if (call.get_state() == ACP_STATE_PENDING) {
281 } else {
282 ESP_LOGE(TAG, "State not yet implemented: %s",
283 LOG_STR_ARG(alarm_control_panel_state_to_string(*call.get_state())));
284 }
285 }
286}
287
288} // namespace esphome::template_
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
bool empty() const
Definition helpers.h:385
size_t size() const
Definition helpers.h:384
const optional< AlarmControlPanelState > & get_state() const
bool is_state_armed(AlarmControlPanelState state)
void publish_state(AlarmControlPanelState state)
Set the state of the alarm_control_panel.
Base class for all binary_sensor-type classes.
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
void arm_(optional< std::string > code, alarm_control_panel::AlarmControlPanelState state, uint32_t delay)
void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags=0, AlarmSensorType type=ALARM_SENSOR_TYPE_DELAYED)
Add a binary_sensor to the alarm_panel.
void control(const alarm_control_panel::AlarmControlPanelCall &call) override
uint16_t type
uint16_t flags
bool state
Definition fan.h:2
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT", "ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN")
const LogString * alarm_control_panel_state_to_string(AlarmControlPanelState state)
Returns a string representation of the state.
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25