ESPHome 2026.5.0-dev
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 sensor pointer, flags, and type in the per-sensor info structure.
21 AlarmSensor alarm_sensor;
22 alarm_sensor.sensor = sensor;
23 alarm_sensor.info.flags = flags;
24 alarm_sensor.info.type = type;
25 alarm_sensor.info.chime_active = false;
26 alarm_sensor.info.auto_bypassed = false;
27 this->sensors_.push_back(alarm_sensor);
28};
29
30// Alarm sensor type strings indexed by AlarmSensorType enum (0-3): DELAYED, INSTANT, DELAYED_FOLLOWER, INSTANT_ALWAYS
31PROGMEM_STRING_TABLE(AlarmSensorTypeStrings, "delayed", "instant", "delayed_follower", "instant_always");
32
33static const LogString *sensor_type_to_string(AlarmSensorType type) {
34 return AlarmSensorTypeStrings::get_log_str(static_cast<uint8_t>(type), 0);
35}
36#endif
37
39 ESP_LOGCONFIG(TAG,
40 "TemplateAlarmControlPanel:\n"
41 " Current State: %s\n"
42 " Number of Codes: %zu\n"
43 " Requires Code To Arm: %s\n"
44 " Arming Away Time: %" PRIu32 "s\n"
45 " Arming Home Time: %" PRIu32 "s\n"
46 " Arming Night Time: %" PRIu32 "s\n"
47 " Pending Time: %" PRIu32 "s\n"
48 " Trigger Time: %" PRIu32 "s\n"
49 " Supported Features: %" PRIu32,
51 YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
52 (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
53 (this->trigger_time_ / 1000), this->get_supported_features());
54#ifdef USE_BINARY_SENSOR
55 for (const auto &alarm_sensor : this->sensors_) {
56 const uint8_t flags = alarm_sensor.info.flags;
57 ESP_LOGCONFIG(TAG,
58 " Binary Sensor:\n"
59 " Name: %s\n"
60 " Type: %s\n"
61 " Armed home bypass: %s\n"
62 " Armed night bypass: %s\n"
63 " Auto bypass: %s\n"
64 " Chime mode: %s",
65 alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
69 }
70#endif
71}
72
76 uint8_t value;
78 if (this->pref_.load(&value)) {
80 }
81 }
82 this->desired_state_ = this->current_state_;
83}
84
86 // change from ARMING to ARMED_x after the arming_time_ has passed
87 if (this->current_state_ == ACP_STATE_ARMING) {
88 auto delay = this->arming_away_time_;
91 }
94 }
95 if ((millis() - this->last_update_) > delay) {
97 this->publish_state(this->desired_state_);
98 }
99 return;
100 }
101 // change from PENDING to TRIGGERED after the delay_time_ has passed
102 if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
104 return;
105 }
106 auto next_state = this->current_state_;
107 // reset triggered if all clear
108 if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
109 (millis() - this->last_update_) > this->trigger_time_) {
110 next_state = this->desired_state_;
111 }
112
113 bool delayed_sensor_faulted = false;
114 bool instant_sensor_faulted = false;
115
116#ifdef USE_BINARY_SENSOR
117 // Test all of the sensors regardless of the alarm panel state
118 for (auto &alarm_sensor : this->sensors_) {
119 auto &info = alarm_sensor.info;
120 auto *sensor = alarm_sensor.sensor;
121 // Check for chime zones
122 if (info.flags & BINARY_SENSOR_MODE_CHIME) {
123 // Look for the transition from closed to open
124 if ((!info.chime_active) && (sensor->state)) {
125 // Must be disarmed to chime
126 if (this->current_state_ == ACP_STATE_DISARMED) {
127 this->chime_callback_.call();
128 }
129 }
130 // Record the sensor state change
131 info.chime_active = sensor->state;
132 }
133 // Check for faulted sensors
134 if (sensor->state) {
135 // Skip if auto bypassed
136 if (info.auto_bypassed) {
137 continue;
138 }
139 // Skip if bypass armed home
141 continue;
142 }
143 // Skip if bypass armed night
145 continue;
146 }
147
148 switch (info.type) {
150 next_state = ACP_STATE_TRIGGERED;
151 [[fallthrough]];
153 instant_sensor_faulted = true;
154 break;
156 // Look to see if we are in the pending state
157 if (this->current_state_ == ACP_STATE_PENDING) {
158 delayed_sensor_faulted = true;
159 } else {
160 instant_sensor_faulted = true;
161 }
162 break;
164 default:
165 delayed_sensor_faulted = true;
166 }
167 }
168 }
169 // Update all sensors ready flag
170 bool sensors_ready = !(instant_sensor_faulted || delayed_sensor_faulted);
171
172 // Call the ready state change callback if there was a change
173 if (this->sensors_ready_ != sensors_ready) {
174 this->sensors_ready_ = sensors_ready;
175 this->ready_callback_.call();
176 }
177
178#endif
179 if (this->is_state_armed(next_state) && (!this->sensors_ready_)) {
180 // Instant sensors
181 if (instant_sensor_faulted) {
183 } else if (delayed_sensor_faulted) {
184 // Delayed sensors
185 if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) {
187 } else {
189 }
190 }
191 } else if (next_state != this->current_state_) {
192 this->publish_state(next_state);
193 }
194}
195
196bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
197 if (!this->codes_.empty()) {
198 if (code.has_value()) {
199 ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
200 // Use strcmp for const char* comparison
201 const char *code_cstr = code.value().c_str();
202 for (const char *stored_code : this->codes_) {
203 if (strcmp(stored_code, code_cstr) == 0)
204 return true;
205 }
206 return false;
207 }
208 ESP_LOGD(TAG, "No code provided");
209 return false;
210 }
211 return true;
212}
213
216 if (this->supports_arm_home_) {
217 features |= ACP_FEAT_ARM_HOME;
218 }
219 if (this->supports_arm_night_) {
220 features |= ACP_FEAT_ARM_NIGHT;
221 }
222 return features;
223}
224
226 uint32_t delay) {
227 if (this->current_state_ != ACP_STATE_DISARMED) {
228 ESP_LOGW(TAG, "Cannot arm when not disarmed");
229 return;
230 }
231 if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
232 ESP_LOGW(TAG, "Not arming code doesn't match");
233 return;
234 }
235 this->desired_state_ = state;
236 if (delay > 0) {
238 } else {
239 this->auto_bypass_sensors_();
240 this->publish_state(state);
241 }
242}
243
245#ifdef USE_BINARY_SENSOR
246 for (auto &alarm_sensor : this->sensors_) {
247 auto &info = alarm_sensor.info;
248 auto *sensor = alarm_sensor.sensor;
249 // Check for faulted bypass_auto sensors and remove them from monitoring
250 if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
251 ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
252 info.auto_bypassed = true;
253 }
254 }
255#endif
256}
257
259#ifdef USE_BINARY_SENSOR
260 for (auto &alarm_sensor : this->sensors_) {
261 alarm_sensor.info.auto_bypassed = false;
262 }
263#endif
264}
265
267 auto opt_state = call.get_state();
268 if (opt_state) {
269 auto state = *opt_state;
271 this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
272 } else if (state == ACP_STATE_ARMED_HOME) {
273 this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
274 } else if (state == ACP_STATE_ARMED_NIGHT) {
275 this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_);
276 } else if (state == ACP_STATE_DISARMED) {
277 if (!this->is_code_valid_(call.get_code())) {
278 ESP_LOGW(TAG, "Not disarming code doesn't match");
279 return;
280 }
284 } else if (state == ACP_STATE_TRIGGERED) {
286 } else if (state == ACP_STATE_PENDING) {
288 } else {
289 ESP_LOGE(TAG, "State not yet implemented: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)));
290 }
291 }
292}
293
294} // 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:680
size_t size() const
Definition helpers.h:679
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.
void arm_(optional< std::string > code, alarm_control_panel::AlarmControlPanelState state, uint32_t delay)
void add_sensor(binary_sensor::BinarySensor *sensor, uint8_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 HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
static void uint32_t