diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index 0839e49875..52f8ac7894 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -31,6 +31,7 @@ ClearedTrigger = alarm_control_panel_ns.class_( ) ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) +ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action) DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action) PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action) TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action) @@ -117,6 +118,18 @@ async def alarm_action_arm_home_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "alarm_control_panel.arm_night", ArmNightAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA +) +async def alarm_action_arm_night_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_CODE in config: + templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) + cg.add(var.set_code(templatable_)) + return var + + @automation.register_action( "alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA ) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index eb50c4f4b5..b1d2b2a097 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() { this->state_.reset(); return; } + if (state == ACP_STATE_ARMED_NIGHT && (this->parent_->get_supported_features() & ACP_FEAT_ARM_NIGHT) == 0) { + ESP_LOGW(TAG, "Cannot arm night when not supported"); + this->state_.reset(); + return; + } } } diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp index 231e7228e1..abe6f51995 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp @@ -12,7 +12,7 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat case ACP_STATE_ARMED_AWAY: return LOG_STR("ARMED_AWAY"); case ACP_STATE_ARMED_NIGHT: - return LOG_STR("NIGHT"); + return LOG_STR("ARMED_NIGHT"); case ACP_STATE_ARMED_VACATION: return LOG_STR("ARMED_VACATION"); case ACP_STATE_ARMED_CUSTOM_BYPASS: diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index 4368129609..81ac584f71 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -67,6 +67,26 @@ template class ArmHomeAction : public Action { AlarmControlPanel *alarm_control_panel_; }; +template class ArmNightAction : public Action { + public: + explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} + + TEMPLATABLE_VALUE(std::string, code) + + void play(Ts... x) override { + auto call = this->alarm_control_panel_->make_call(); + auto code = this->code_.optional_value(x...); + if (code.has_value()) { + call.set_code(code.value()); + } + call.arm_night(); + call.perform(); + } + + protected: + AlarmControlPanel *alarm_control_panel_; +}; + template class DisarmAction : public Action { public: explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} diff --git a/esphome/components/template/alarm_control_panel/__init__.py b/esphome/components/template/alarm_control_panel/__init__.py index 5156a0832a..27b7e92b4f 100644 --- a/esphome/components/template/alarm_control_panel/__init__.py +++ b/esphome/components/template/alarm_control_panel/__init__.py @@ -16,18 +16,22 @@ CODEOWNERS = ["@grahambrown11"] CONF_CODES = "codes" CONF_BYPASS_ARMED_HOME = "bypass_armed_home" +CONF_BYPASS_ARMED_NIGHT = "bypass_armed_night" CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm" CONF_ARMING_HOME_TIME = "arming_home_time" +CONF_ARMING_NIGHT_TIME = "arming_night_time" CONF_ARMING_AWAY_TIME = "arming_away_time" CONF_PENDING_TIME = "pending_time" CONF_TRIGGER_TIME = "trigger_time" FLAG_NORMAL = "normal" FLAG_BYPASS_ARMED_HOME = "bypass_armed_home" +FLAG_BYPASS_ARMED_NIGHT = "bypass_armed_night" BinarySensorFlags = { FLAG_NORMAL: 1 << 0, FLAG_BYPASS_ARMED_HOME: 1 << 1, + FLAG_BYPASS_ARMED_NIGHT: 1 << 2, } TemplateAlarmControlPanel = template_ns.class_( @@ -55,6 +59,7 @@ TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA = cv.maybe_simple_value( { cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor), cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean, + cv.Optional(CONF_BYPASS_ARMED_NIGHT, default=False): cv.boolean, }, key=CONF_INPUT, ) @@ -66,6 +71,7 @@ TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA = ( cv.Optional(CONF_CODES): cv.ensure_list(cv.string_strict), cv.Optional(CONF_REQUIRES_CODE_TO_ARM): cv.boolean, cv.Optional(CONF_ARMING_HOME_TIME): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ARMING_NIGHT_TIME): cv.positive_time_period_milliseconds, cv.Optional( CONF_ARMING_AWAY_TIME, default="0s" ): cv.positive_time_period_milliseconds, @@ -110,14 +116,23 @@ async def to_code(config): cg.add(var.set_arming_home_time(config[CONF_ARMING_HOME_TIME])) supports_arm_home = True + supports_arm_night = False + if CONF_ARMING_NIGHT_TIME in config: + cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME])) + supports_arm_night = True + for sensor in config.get(CONF_BINARY_SENSORS, []): bs = await cg.get_variable(sensor[CONF_INPUT]) flags = BinarySensorFlags[FLAG_NORMAL] if sensor[CONF_BYPASS_ARMED_HOME]: flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME] supports_arm_home = True + if sensor[CONF_BYPASS_ARMED_NIGHT]: + flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_NIGHT] + supports_arm_night = True cg.add(var.add_sensor(bs, flags)) cg.add(var.set_supports_arm_home(supports_arm_home)) + cg.add(var.set_supports_arm_night(supports_arm_night)) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index 1c54998e42..da56976b56 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -29,6 +29,8 @@ void TemplateAlarmControlPanel::dump_config() { ESP_LOGCONFIG(TAG, " Arming Away Time: %us", (this->arming_away_time_ / 1000)); if (this->arming_home_time_ != 0) ESP_LOGCONFIG(TAG, " Arming Home Time: %us", (this->arming_home_time_ / 1000)); + if (this->arming_night_time_ != 0) + ESP_LOGCONFIG(TAG, " Arming Night Time: %us", (this->arming_night_time_ / 1000)); ESP_LOGCONFIG(TAG, " Pending Time: %us", (this->pending_time_ / 1000)); ESP_LOGCONFIG(TAG, " Trigger Time: %us", (this->trigger_time_ / 1000)); ESP_LOGCONFIG(TAG, " Supported Features: %u", this->get_supported_features()); @@ -38,6 +40,8 @@ void TemplateAlarmControlPanel::dump_config() { ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str()); ESP_LOGCONFIG(TAG, " Armed home bypass: %s", TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)); + ESP_LOGCONFIG(TAG, " Armed night bypass: %s", + TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)); } #endif } @@ -69,6 +73,9 @@ void TemplateAlarmControlPanel::loop() { if (this->desired_state_ == ACP_STATE_ARMED_HOME) { delay = this->arming_home_time_; } + if (this->desired_state_ == ACP_STATE_ARMED_NIGHT) { + delay = this->arming_night_time_; + } if ((millis() - this->last_update_) > delay) { this->publish_state(this->desired_state_); } @@ -95,6 +102,10 @@ void TemplateAlarmControlPanel::loop() { (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) { continue; } + if (this->current_state_ == ACP_STATE_ARMED_NIGHT && + (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) { + continue; + } trigger = true; break; } @@ -129,6 +140,9 @@ uint32_t TemplateAlarmControlPanel::get_supported_features() const { if (this->supports_arm_home_) { features |= ACP_FEAT_ARM_HOME; } + if (this->supports_arm_night_) { + features |= ACP_FEAT_ARM_NIGHT; + } return features; } @@ -158,6 +172,8 @@ void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) { this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_); } else if (call.get_state() == ACP_STATE_ARMED_HOME) { this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_); + } else if (call.get_state() == ACP_STATE_ARMED_NIGHT) { + this->arm_(call.get_code(), ACP_STATE_ARMED_NIGHT, this->arming_night_time_); } else if (call.get_state() == ACP_STATE_DISARMED) { if (!this->is_code_valid_(call.get_code())) { ESP_LOGW(TAG, "Not disarming code doesn't match"); diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 4065356ba8..ebd8696692 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -19,6 +19,7 @@ namespace template_ { enum BinarySensorFlags : uint16_t { BINARY_SENSOR_MODE_NORMAL = 1 << 0, BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1, + BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT = 1 << 2, }; #endif @@ -71,6 +72,12 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, */ void set_arming_home_time(uint32_t time) { this->arming_home_time_ = time; } + /** set the delay before arming night + * + * @param time The milliseconds + */ + void set_arming_night_time(uint32_t time) { this->arming_night_time_ = time; } + /** set the delay before triggering * * @param time The milliseconds @@ -85,6 +92,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, void set_supports_arm_home(bool supports_arm_home) { supports_arm_home_ = supports_arm_home; } + void set_supports_arm_night(bool supports_arm_night) { supports_arm_night_ = supports_arm_night; } + protected: void control(const alarm_control_panel::AlarmControlPanelCall &call) override; #ifdef USE_BINARY_SENSOR @@ -97,6 +106,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, uint32_t arming_away_time_; // the arming home delay uint32_t arming_home_time_{0}; + // the arming night delay + uint32_t arming_night_time_{0}; // the trigger delay uint32_t pending_time_; // the time in trigger @@ -106,6 +117,7 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, // requires a code to arm bool requires_code_to_arm_ = false; bool supports_arm_home_ = false; + bool supports_arm_night_ = false; // check if the code is valid bool is_code_valid_(optional code); diff --git a/tests/test3.yaml b/tests/test3.yaml index f7b66a748e..3ab1d561b3 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1194,12 +1194,14 @@ alarm_control_panel: - "1234" requires_code_to_arm: true arming_home_time: 1s + arming_night_time: 1s arming_away_time: 15s pending_time: 15s trigger_time: 30s binary_sensors: - input: bin1 bypass_armed_home: true + bypass_armed_night: true on_state: then: - lambda: !lambda |-