ESPHome 2025.6.3
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
9namespace esphome {
10namespace template_ {
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 this->sensor_map_[sensor].flags = flags;
24 this->sensor_map_[sensor].type = type;
25 this->sensor_data_.push_back(sd);
26 this->sensor_map_[sensor].store_index = this->next_store_index_++;
27};
28#endif
29
31 ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:");
32 ESP_LOGCONFIG(TAG,
33 " Current State: %s\n"
34 " Number of Codes: %u",
35 LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size());
36 if (!this->codes_.empty())
37 ESP_LOGCONFIG(TAG, " Requires Code To Arm: %s", YESNO(this->requires_code_to_arm_));
38 ESP_LOGCONFIG(TAG, " Arming Away Time: %" PRIu32 "s", (this->arming_away_time_ / 1000));
39 if (this->arming_home_time_ != 0)
40 ESP_LOGCONFIG(TAG, " Arming Home Time: %" PRIu32 "s", (this->arming_home_time_ / 1000));
41 if (this->arming_night_time_ != 0)
42 ESP_LOGCONFIG(TAG, " Arming Night Time: %" PRIu32 "s", (this->arming_night_time_ / 1000));
43 ESP_LOGCONFIG(TAG,
44 " Pending Time: %" PRIu32 "s\n"
45 " Trigger Time: %" PRIu32 "s\n"
46 " Supported Features: %" PRIu32,
47 (this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features());
48#ifdef USE_BINARY_SENSOR
49 for (auto sensor_info : this->sensor_map_) {
50 ESP_LOGCONFIG(TAG, " Binary Sensor:");
51 ESP_LOGCONFIG(TAG,
52 " Name: %s\n"
53 " Armed home bypass: %s\n"
54 " Armed night bypass: %s\n"
55 " Auto bypass: %s\n"
56 " Chime mode: %s",
57 sensor_info.first->get_name().c_str(),
58 TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
59 TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
60 TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
61 TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME));
62 const char *sensor_type;
63 switch (sensor_info.second.type) {
65 sensor_type = "instant";
66 break;
68 sensor_type = "delayed_follower";
69 break;
71 sensor_type = "instant_always";
72 break;
74 default:
75 sensor_type = "delayed";
76 }
77 ESP_LOGCONFIG(TAG, " Sensor type: %s", sensor_type);
78 }
79#endif
80}
81
83 ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->name_.c_str());
84 switch (this->restore_mode_) {
87 break;
89 uint8_t value;
91 if (this->pref_.load(&value)) {
93 } else {
95 }
96 break;
97 }
98 }
99 this->desired_state_ = this->current_state_;
100}
101
103 // change from ARMING to ARMED_x after the arming_time_ has passed
104 if (this->current_state_ == ACP_STATE_ARMING) {
105 auto delay = this->arming_away_time_;
107 delay = this->arming_home_time_;
108 }
111 }
112 if ((millis() - this->last_update_) > delay) {
113 this->bypass_before_arming();
114 this->publish_state(this->desired_state_);
115 }
116 return;
117 }
118 // change from PENDING to TRIGGERED after the delay_time_ has passed
119 if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
121 return;
122 }
123 auto future_state = this->current_state_;
124 // reset triggered if all clear
125 if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
126 (millis() - this->last_update_) > this->trigger_time_) {
127 future_state = this->desired_state_;
128 }
129
130 bool delayed_sensor_not_ready = false;
131 bool instant_sensor_not_ready = false;
132
133#ifdef USE_BINARY_SENSOR
134 // Test all of the sensors in the list regardless of the alarm panel state
135 for (auto sensor_info : this->sensor_map_) {
136 // Check for chime zones
137 if ((sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)) {
138 // Look for the transition from closed to open
139 if ((!this->sensor_data_[sensor_info.second.store_index].last_chime_state) && (sensor_info.first->state)) {
140 // Must be disarmed to chime
141 if (this->current_state_ == ACP_STATE_DISARMED) {
142 this->chime_callback_.call();
143 }
144 }
145 // Record the sensor state change
146 this->sensor_data_[sensor_info.second.store_index].last_chime_state = sensor_info.first->state;
147 }
148 // Check for triggered sensors
149 if (sensor_info.first->state) { // Sensor triggered?
150 // Skip if auto bypassed
151 if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(),
152 sensor_info.second.store_index) == 1) {
153 continue;
154 }
155 // Skip if bypass armed home
157 (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
158 continue;
159 }
160 // Skip if bypass armed night
162 (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
163 continue;
164 }
165
166 switch (sensor_info.second.type) {
168 instant_sensor_not_ready = true;
169 break;
171 instant_sensor_not_ready = true;
172 future_state = ACP_STATE_TRIGGERED;
173 break;
175 // Look to see if we are in the pending state
176 if (this->current_state_ == ACP_STATE_PENDING) {
177 delayed_sensor_not_ready = true;
178 } else {
179 instant_sensor_not_ready = true;
180 }
181 break;
183 default:
184 delayed_sensor_not_ready = true;
185 }
186 }
187 }
188 // Update all sensors not ready flag
189 this->sensors_ready_ = ((!instant_sensor_not_ready) && (!delayed_sensor_not_ready));
190
191 // Call the ready state change callback if there was a change
192 if (this->sensors_ready_ != this->sensors_ready_last_) {
193 this->ready_callback_.call();
195 }
196
197#endif
198 if (this->is_state_armed(future_state) && (!this->sensors_ready_)) {
199 // Instant sensors
200 if (instant_sensor_not_ready) {
202 } else if (delayed_sensor_not_ready) {
203 // Delayed sensors
204 if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) {
206 } else {
208 }
209 }
210 } else if (future_state != this->current_state_) {
211 this->publish_state(future_state);
212 }
213}
214
216 if (!this->codes_.empty()) {
217 if (code.has_value()) {
218 ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
219 return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
220 }
221 ESP_LOGD(TAG, "No code provided");
222 return false;
223 }
224 return true;
225}
226
228 uint32_t features = ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER;
229 if (this->supports_arm_home_) {
230 features |= ACP_FEAT_ARM_HOME;
231 }
232 if (this->supports_arm_night_) {
233 features |= ACP_FEAT_ARM_NIGHT;
234 }
235 return features;
236}
237
238bool TemplateAlarmControlPanel::get_requires_code() const { return !this->codes_.empty(); }
239
241 uint32_t delay) {
242 if (this->current_state_ != ACP_STATE_DISARMED) {
243 ESP_LOGW(TAG, "Cannot arm when not disarmed");
244 return;
245 }
246 if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
247 ESP_LOGW(TAG, "Not arming code doesn't match");
248 return;
249 }
250 this->desired_state_ = state;
251 if (delay > 0) {
253 } else {
254 this->bypass_before_arming();
255 this->publish_state(state);
256 }
257}
258
260#ifdef USE_BINARY_SENSOR
261 for (auto sensor_info : this->sensor_map_) {
262 // Check for sensors left on and set to bypass automatically and remove them from monitoring
263 if ((sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor_info.first->state)) {
264 ESP_LOGW(TAG, "'%s' is left on and will be automatically bypassed", sensor_info.first->get_name().c_str());
265 this->bypassed_sensor_indicies_.push_back(sensor_info.second.store_index);
266 }
267 }
268#endif
269}
270
272 if (call.get_state()) {
273 if (call.get_state() == ACP_STATE_ARMED_AWAY) {
274 this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
275 } else if (call.get_state() == ACP_STATE_ARMED_HOME) {
276 this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
277 } else if (call.get_state() == ACP_STATE_ARMED_NIGHT) {
278 this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_);
279 } else if (call.get_state() == ACP_STATE_DISARMED) {
280 if (!this->is_code_valid_(call.get_code())) {
281 ESP_LOGW(TAG, "Not disarming code doesn't match");
282 return;
283 }
286#ifdef USE_BINARY_SENSOR
287 this->bypassed_sensor_indicies_.clear();
288#endif
289 } else if (call.get_state() == ACP_STATE_TRIGGERED) {
291 } else if (call.get_state() == ACP_STATE_PENDING) {
293 } else {
294 ESP_LOGE(TAG, "State not yet implemented: %s",
295 LOG_STR_ARG(alarm_control_panel_state_to_string(*call.get_state())));
296 }
297 }
298}
299
300} // namespace template_
301} // namespace esphome
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
uint32_t get_object_id_hash()
constexpr const char * c_str() const
Definition string_ref.h:69
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:87
value_type const & value() const
Definition optional.h:89
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.
std::map< binary_sensor::BinarySensor *, SensorInfo > sensor_map_
void control(const alarm_control_panel::AlarmControlPanelCall &call) override
uint8_t type
bool state
Definition fan.h:0
const LogString * alarm_control_panel_state_to_string(AlarmControlPanelState state)
Returns a string representation of the state.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28