ESPHome 2026.11.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
14ESPHOME_LOG_TAG(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 size_t num_codes = 0;
40 if (this->codes_ != nullptr) {
41 while (this->codes_[num_codes] != nullptr)
42 num_codes++;
43 }
44 ESP_LOGCONFIG(TAG,
45 "TemplateAlarmControlPanel:\n"
46 " Current State: %s\n"
47 " Number of Codes: %zu\n"
48 " Requires Code To Arm: %s\n"
49 " Arming Away Time: %" PRIu32 "s\n"
50 " Arming Home Time: %" PRIu32 "s\n"
51 " Arming Night Time: %" PRIu32 "s\n"
52 " Pending Time: %" PRIu32 "s\n"
53 " Trigger Time: %" PRIu32 "s\n"
54 " Supported Features: %" PRIu32,
55 LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), num_codes,
56 YESNO(this->codes_ != nullptr && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
57 (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
58 (this->trigger_time_ / 1000), this->get_supported_features());
59#ifdef USE_BINARY_SENSOR
60 for (const auto &alarm_sensor : this->sensors_) {
61 const uint8_t flags = alarm_sensor.info.flags;
62 ESP_LOGCONFIG(TAG,
63 " Binary Sensor:\n"
64 " Name: %s\n"
65 " Type: %s\n"
66 " Armed home bypass: %s\n"
67 " Armed night bypass: %s\n"
68 " Auto bypass: %s\n"
69 " Chime mode: %s",
70 LOG_STR_ARG(alarm_sensor.sensor->get_log_name()),
71 LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
75 }
76#endif
77}
78
82 uint8_t value;
84 if (this->pref_.load(&value)) {
86 }
87 }
88 this->desired_state_ = this->current_state_;
89}
90
92 // change from ARMING to ARMED_x after the arming_time_ has passed
93 if (this->current_state_ == ACP_STATE_ARMING) {
94 auto delay = this->arming_away_time_;
97 }
100 }
101 if ((millis() - this->last_update_) > delay) {
102 this->auto_bypass_sensors_();
103 this->publish_state(this->desired_state_);
104 }
105 return;
106 }
107 // change from PENDING to TRIGGERED after the delay_time_ has passed
108 if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
110 return;
111 }
112 auto next_state = this->current_state_;
113 // reset triggered if all clear
114 if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
115 (millis() - this->last_update_) > this->trigger_time_) {
116 next_state = this->desired_state_;
117 }
118
119 bool delayed_sensor_faulted = false;
120 bool instant_sensor_faulted = false;
121
122#ifdef USE_BINARY_SENSOR
123 // Test all of the sensors regardless of the alarm panel state
124 for (auto &alarm_sensor : this->sensors_) {
125 auto &info = alarm_sensor.info;
126 auto *sensor = alarm_sensor.sensor;
127 // Check for chime zones
128 if (info.flags & BINARY_SENSOR_MODE_CHIME) {
129 // Look for the transition from closed to open
130 if ((!info.chime_active) && (sensor->state)) {
131 // Must be disarmed to chime
132 if (this->current_state_ == ACP_STATE_DISARMED) {
133 this->chime_callback_.call();
134 }
135 }
136 // Record the sensor state change
137 info.chime_active = sensor->state;
138 }
139 // Check for faulted sensors
140 if (sensor->state) {
141 // Skip if auto bypassed
142 if (info.auto_bypassed) {
143 continue;
144 }
145 // Skip if bypass armed home
147 continue;
148 }
149 // Skip if bypass armed night
151 continue;
152 }
153
154 switch (info.type) {
156 next_state = ACP_STATE_TRIGGERED;
157 [[fallthrough]];
159 instant_sensor_faulted = true;
160 break;
162 // Look to see if we are in the pending state
163 if (this->current_state_ == ACP_STATE_PENDING) {
164 delayed_sensor_faulted = true;
165 } else {
166 instant_sensor_faulted = true;
167 }
168 break;
170 default:
171 delayed_sensor_faulted = true;
172 }
173 }
174 }
175 // Update all sensors ready flag
176 bool sensors_ready = !(instant_sensor_faulted || delayed_sensor_faulted);
177
178 // Call the ready state change callback if there was a change
179 if (this->sensors_ready_ != sensors_ready) {
180 this->sensors_ready_ = sensors_ready;
181 this->ready_callback_.call();
182 }
183
184#endif
185 if (this->is_state_armed(next_state) && (!this->sensors_ready_)) {
186 // Instant sensors
187 if (instant_sensor_faulted) {
189 } else if (delayed_sensor_faulted) {
190 // Delayed sensors
191 if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) {
193 } else {
195 }
196 }
197 } else if (next_state != this->current_state_) {
198 this->publish_state(next_state);
199 }
200}
201
202bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
203 if (this->codes_ != nullptr) {
204 if (code.has_value()) {
205 ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
206 const char *code_cstr = code.value().c_str();
207 for (const char *const *stored = this->codes_; *stored != nullptr; stored++) {
208 if (strcmp(*stored, code_cstr) == 0)
209 return true;
210 }
211 return false;
212 }
213 ESP_LOGD(TAG, "No code provided");
214 return false;
215 }
216 return true;
217}
218
221 if (this->supports_arm_home_) {
222 features |= ACP_FEAT_ARM_HOME;
223 }
224 if (this->supports_arm_night_) {
225 features |= ACP_FEAT_ARM_NIGHT;
226 }
227 return features;
228}
229
231 uint32_t delay) {
232 if (this->current_state_ != ACP_STATE_DISARMED) {
233 ESP_LOGW(TAG, "Cannot arm when not disarmed");
234 return;
235 }
236 if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
237 ESP_LOGW(TAG, "Not arming code doesn't match");
238 return;
239 }
240 this->desired_state_ = state;
241 if (delay > 0) {
243 } else {
244 this->auto_bypass_sensors_();
245 this->publish_state(state);
246 }
247}
248
250#ifdef USE_BINARY_SENSOR
251 for (auto &alarm_sensor : this->sensors_) {
252 auto &info = alarm_sensor.info;
253 auto *sensor = alarm_sensor.sensor;
254 // Check for faulted bypass_auto sensors and remove them from monitoring
255 if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
256 ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", LOG_STR_ARG(sensor->get_log_name()));
257 info.auto_bypassed = true;
258 }
259 }
260#endif
261}
262
264#ifdef USE_BINARY_SENSOR
265 for (auto &alarm_sensor : this->sensors_) {
266 alarm_sensor.info.auto_bypassed = false;
267 }
268#endif
269}
270
272 auto opt_state = call.get_state();
273 if (opt_state) {
274 auto state = *opt_state;
276 this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
277 } else if (state == ACP_STATE_ARMED_HOME) {
278 this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
279 } else if (state == ACP_STATE_ARMED_NIGHT) {
280 this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_);
281 } else if (state == ACP_STATE_DISARMED) {
282 if (!this->is_code_valid_(call.get_code())) {
283 ESP_LOGW(TAG, "Not disarming code doesn't match");
284 return;
285 }
289 } else if (state == ACP_STATE_TRIGGERED) {
291 } else if (state == ACP_STATE_PENDING) {
293 } else {
294 ESP_LOGE(TAG, "State not yet implemented: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)));
295 }
296 }
297}
298
299} // namespace esphome::template_
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
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 flags
uint8_t type
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.
ESPHOME_LOG_TAG(TAG, "alarm_control_panel")
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t