ESPHome 2025.12.1
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"
8
10
11using namespace esphome::alarm_control_panel;
12
13static const char *const TAG = "template.alarm_control_panel";
14
16
17#ifdef USE_BINARY_SENSOR
19 // Save the flags and type. Assign a store index for the per sensor data type.
21 sd.last_chime_state = false;
22 AlarmSensor alarm_sensor;
23 alarm_sensor.sensor = sensor;
24 alarm_sensor.info.flags = flags;
25 alarm_sensor.info.type = type;
26 alarm_sensor.info.store_index = this->next_store_index_++;
27 this->sensors_.push_back(alarm_sensor);
28 this->sensor_data_.push_back(sd);
29};
30
31static const LogString *sensor_type_to_string(AlarmSensorType type) {
32 switch (type) {
34 return LOG_STR("instant");
36 return LOG_STR("delayed_follower");
38 return LOG_STR("instant_always");
40 default:
41 return LOG_STR("delayed");
42 }
43}
44#endif
45
47 ESP_LOGCONFIG(TAG,
48 "TemplateAlarmControlPanel:\n"
49 " Current State: %s\n"
50 " Number of Codes: %zu\n"
51 " Requires Code To Arm: %s\n"
52 " Arming Away Time: %" PRIu32 "s\n"
53 " Arming Home Time: %" PRIu32 "s\n"
54 " Arming Night Time: %" PRIu32 "s\n"
55 " Pending Time: %" PRIu32 "s\n"
56 " Trigger Time: %" PRIu32 "s\n"
57 " Supported Features: %" PRIu32,
58 LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size(),
59 YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000),
60 (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
61 (this->trigger_time_ / 1000), this->get_supported_features());
62#ifdef USE_BINARY_SENSOR
63 for (const auto &alarm_sensor : this->sensors_) {
64 const uint16_t flags = alarm_sensor.info.flags;
65 ESP_LOGCONFIG(TAG,
66 " Binary Sensor:\n"
67 " Name: %s\n"
68 " Type: %s\n"
69 " Armed home bypass: %s\n"
70 " Armed night bypass: %s\n"
71 " Auto bypass: %s\n"
72 " Chime mode: %s",
73 alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
77 }
78#endif
79}
80
84 uint8_t value;
86 if (this->pref_.load(&value)) {
88 }
89 }
90 this->desired_state_ = this->current_state_;
91}
92
94 // change from ARMING to ARMED_x after the arming_time_ has passed
95 if (this->current_state_ == ACP_STATE_ARMING) {
96 auto delay = this->arming_away_time_;
99 }
102 }
103 if ((millis() - this->last_update_) > delay) {
104 this->bypass_before_arming();
105 this->publish_state(this->desired_state_);
106 }
107 return;
108 }
109 // change from PENDING to TRIGGERED after the delay_time_ has passed
110 if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
112 return;
113 }
114 auto next_state = this->current_state_;
115 // reset triggered if all clear
116 if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
117 (millis() - this->last_update_) > this->trigger_time_) {
118 next_state = this->desired_state_;
119 }
120
121 bool delayed_sensor_faulted = false;
122 bool instant_sensor_faulted = false;
123
124#ifdef USE_BINARY_SENSOR
125 // Test all of the sensors regardless of the alarm panel state
126 for (const auto &alarm_sensor : this->sensors_) {
127 const auto &info = alarm_sensor.info;
128 auto *sensor = alarm_sensor.sensor;
129 // Check for chime zones
130 if (info.flags & BINARY_SENSOR_MODE_CHIME) {
131 // Look for the transition from closed to open
132 if ((!this->sensor_data_[info.store_index].last_chime_state) && (sensor->state)) {
133 // Must be disarmed to chime
134 if (this->current_state_ == ACP_STATE_DISARMED) {
135 this->chime_callback_.call();
136 }
137 }
138 // Record the sensor state change
139 this->sensor_data_[info.store_index].last_chime_state = sensor->state;
140 }
141 // Check for faulted sensors
142 if (sensor->state) {
143 // Skip if auto bypassed
144 if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(),
145 info.store_index) == 1) {
146 continue;
147 }
148 // Skip if bypass armed home
150 continue;
151 }
152 // Skip if bypass armed night
154 continue;
155 }
156
157 switch (info.type) {
159 next_state = ACP_STATE_TRIGGERED;
160 [[fallthrough]];
162 instant_sensor_faulted = true;
163 break;
165 // Look to see if we are in the pending state
166 if (this->current_state_ == ACP_STATE_PENDING) {
167 delayed_sensor_faulted = true;
168 } else {
169 instant_sensor_faulted = true;
170 }
171 break;
173 default:
174 delayed_sensor_faulted = true;
175 }
176 }
177 }
178 // Update all sensors ready flag
179 bool sensors_ready = !(instant_sensor_faulted || delayed_sensor_faulted);
180
181 // Call the ready state change callback if there was a change
182 if (this->sensors_ready_ != sensors_ready) {
183 this->sensors_ready_ = sensors_ready;
184 this->ready_callback_.call();
185 }
186
187#endif
188 if (this->is_state_armed(next_state) && (!this->sensors_ready_)) {
189 // Instant sensors
190 if (instant_sensor_faulted) {
192 } else if (delayed_sensor_faulted) {
193 // Delayed sensors
194 if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) {
196 } else {
198 }
199 }
200 } else if (next_state != this->current_state_) {
201 this->publish_state(next_state);
202 }
203}
204
206 if (!this->codes_.empty()) {
207 if (code.has_value()) {
208 ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
209 return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
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_
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
uint32_t get_preference_hash()
Get a unique hash for storing preferences/settings for this entity.
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:0
const LogString * alarm_control_panel_state_to_string(AlarmControlPanelState state)
Returns a string representation of the state.
ESPPreferences * global_preferences
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:31
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30