mirror of
https://github.com/nuttytree/ESPHome-Devices.git
synced 2024-08-30 18:12:19 +00:00
F/garage door (#45)
* Start work on the garage door controller * More work on the Garage Door * Another update * Get garage door to compilable state * Core functionality is mostly complete * More work on the garage door * Garage door (mostly) ready for testing * Begin debugging * More debugging work * Closer to a working state * Hoping this is close to the final version * Couple more tweaks * Still have some significant bugs * So close!! * Add sensor read delays * Properly handle stopping when endstops are reached * Adjust some timings * Cleanup, more logging, and timing adjustments * Fix typo * Major refactor of the state management and fix the repeated disconnects * More adjustments * Add schematic * Mostly complete * Make device more configurable --------- Co-authored-by: Chris Nussbaum <chris.nussbaum@protolabs.com>
This commit is contained in:
parent
9998ad39db
commit
5820355a7f
0
components/garage_door/__init__.py
Normal file
0
components/garage_door/__init__.py
Normal file
128
components/garage_door/cover.py
Normal file
128
components/garage_door/cover.py
Normal file
@ -0,0 +1,128 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import cover, output, sensor, binary_sensor, rtttl
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_OPEN_DURATION,
|
||||
CONF_CLOSE_DURATION,
|
||||
UNIT_MILLISECOND,
|
||||
DEVICE_CLASS_DURATION,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
ICON_TIMER,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["lock"]
|
||||
|
||||
CONF_CONTROL_OUTPUT = "control_output"
|
||||
CONF_BUTTON_SENSOR = "button_sensor"
|
||||
CONF_CLOSED_SENSOR = "closed_sensor"
|
||||
CONF_OPEN_SENSOR = "open_sensor"
|
||||
CONF_REMOTE_SENSOR = "remote_sensor"
|
||||
CONF_REMOTE_LIGHT_SENSOR = "remote_light_sensor"
|
||||
CONF_WARNING_RTTTL = "warning_rtttl"
|
||||
CONF_CLOSE_WARNING_TONES = "close_warning_tones"
|
||||
DEFAULT_CLOSE_WARNING_TONES = "Imperial:d=4, o=5, b=100:e, e, e, 8c, 16p, 16g, e, 8c, 16p, 16g, e, p, b, b, b, 8c6, 16p, 16g, d#, 8c, 16p, 16g, e, 8p"
|
||||
CONF_CONTROL_ACTIVE_DURATION = "control_active_duration"
|
||||
CONF_CONTROL_INACTIVE_DURATION = "control_inactive_duration"
|
||||
CONF_REVERSES_ON_STOP_OPENING = "reverses_on_stop_opening"
|
||||
CONF_REVERSES_ON_STOP_CLOSING = "reverses_on_stop_closing"
|
||||
CONF_LAST_OPEN_TIME_SENSOR = "last_open_time_sensor"
|
||||
CONF_LAST_CLOSE_TIME_SENSOR = "last_close_time_sensor"
|
||||
DEVICE_CLASS = "garage"
|
||||
|
||||
cover_ns = cg.esphome_ns.namespace("cover")
|
||||
GarageDoor = cover_ns.class_("GarageDoor", cover.Cover, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cover.COVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GarageDoor),
|
||||
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Required(CONF_CONTROL_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Required(CONF_BUTTON_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_CLOSED_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_OPEN_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_REMOTE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_REMOTE_LIGHT_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_WARNING_RTTTL): cv.use_id(rtttl.Rtttl),
|
||||
cv.Optional(CONF_CLOSE_WARNING_TONES, default=DEFAULT_CLOSE_WARNING_TONES): cv.string,
|
||||
cv.Optional(CONF_CONTROL_ACTIVE_DURATION, default="200ms"): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_CONTROL_INACTIVE_DURATION, default="200ms"): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_REVERSES_ON_STOP_OPENING, default="false"): cv.boolean,
|
||||
cv.Optional(CONF_REVERSES_ON_STOP_CLOSING, default="false"): cv.boolean,
|
||||
cv.Optional(CONF_LAST_OPEN_TIME_SENSOR): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_TIMER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_LAST_CLOSE_TIME_SENSOR): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_TIMER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await cover.register_cover(var, config)
|
||||
|
||||
cg.add(var.set_device_class(DEVICE_CLASS))
|
||||
|
||||
cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
|
||||
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
|
||||
|
||||
control_output = await cg.get_variable(config[CONF_CONTROL_OUTPUT])
|
||||
cg.add(var.set_control_output(control_output))
|
||||
|
||||
button_sensor = await cg.get_variable(config[CONF_BUTTON_SENSOR])
|
||||
cg.add(var.set_button_sensor(button_sensor))
|
||||
|
||||
closed_sensor = await cg.get_variable(config[CONF_CLOSED_SENSOR])
|
||||
cg.add(var.set_closed_sensor(closed_sensor))
|
||||
|
||||
open_sensor = await cg.get_variable(config[CONF_OPEN_SENSOR])
|
||||
cg.add(var.set_open_sensor(open_sensor))
|
||||
|
||||
remote_sensor = await cg.get_variable(config[CONF_REMOTE_SENSOR])
|
||||
cg.add(var.set_remote_sensor(remote_sensor))
|
||||
|
||||
remote_light_sensor = await cg.get_variable(config[CONF_REMOTE_LIGHT_SENSOR])
|
||||
cg.add(var.set_remote_light_sensor(remote_light_sensor))
|
||||
|
||||
warning_rtttl = await cg.get_variable(config[CONF_WARNING_RTTTL])
|
||||
cg.add(var.set_warning_rtttl(warning_rtttl))
|
||||
|
||||
close_warning_tones = config[CONF_CLOSE_WARNING_TONES]
|
||||
cg.add(var.set_close_warning_tones(close_warning_tones))
|
||||
|
||||
control_active_duration = config[CONF_CONTROL_ACTIVE_DURATION]
|
||||
cg.add(var.set_control_active_duration(control_active_duration))
|
||||
|
||||
control_inactive_duration = config[CONF_CONTROL_INACTIVE_DURATION]
|
||||
cg.add(var.set_control_inactive_duration(control_inactive_duration))
|
||||
|
||||
reverses_on_stop_opening = config[CONF_REVERSES_ON_STOP_OPENING]
|
||||
cg.add(var.set_reverses_on_stop_opening(reverses_on_stop_opening))
|
||||
|
||||
reverses_on_stop_closing = config[CONF_REVERSES_ON_STOP_CLOSING]
|
||||
cg.add(var.set_reverses_on_stop_closing(reverses_on_stop_closing))
|
||||
|
||||
if CONF_LAST_OPEN_TIME_SENSOR in config:
|
||||
last_open_time_config = config[CONF_LAST_OPEN_TIME_SENSOR]
|
||||
last_open_time_sensor = await sensor.new_sensor(last_open_time_config)
|
||||
cg.add(var.set_last_open_time_sensor(last_open_time_sensor))
|
||||
|
||||
if CONF_LAST_CLOSE_TIME_SENSOR in config:
|
||||
last_close_time_config = config[CONF_LAST_CLOSE_TIME_SENSOR]
|
||||
last_close_time_sensor = await sensor.new_sensor(last_close_time_config)
|
||||
cg.add(var.set_last_close_time_sensor(last_close_time_sensor))
|
557
components/garage_door/garage_door.cpp
Normal file
557
components/garage_door/garage_door.cpp
Normal file
@ -0,0 +1,557 @@
|
||||
#include "garage_door.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
using namespace esphome::lock;
|
||||
|
||||
static const char* TAG = "GarageDoor";
|
||||
|
||||
// Number of milliseconds between publishing the state while the door is opening or closing
|
||||
const uint32_t DOOR_MOVING_PUBLISH_INTERVAL = 1000;
|
||||
|
||||
// Events
|
||||
const std::string EVENT_LOCAL_LIGHT = "local_light_button";
|
||||
const std::string EVENT_REMOTE_LIGHT = "remote_light_button";
|
||||
const std::string EVENT_FAILED_OPEN = "open_failed";
|
||||
const std::string EVENT_FAILED_CLOSE = "close_failed";
|
||||
const std::string EVENT_BUTTON_DISCONNECTED = "button_disconnected";
|
||||
|
||||
void GarageDoorLock::setup() {
|
||||
LockState restored_state{};
|
||||
this->rtc_.load(&restored_state);
|
||||
if (restored_state == LOCK_STATE_LOCKED) {
|
||||
this->garage_door_->lock_();
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoorLock::control(const lock::LockCall &call) {
|
||||
auto state = *call.get_state();
|
||||
if (state == lock::LOCK_STATE_LOCKED) {
|
||||
this->publish_state(lock::LOCK_STATE_LOCKING);
|
||||
this->garage_door_->lock_();
|
||||
} else if (state == lock::LOCK_STATE_UNLOCKED) {
|
||||
this->garage_door_->unlock_();
|
||||
}
|
||||
}
|
||||
|
||||
GarageDoor::GarageDoor() {
|
||||
this->lock_comp_ = new GarageDoorLock(this);
|
||||
App.register_component(this->lock_comp_);
|
||||
App.register_lock(this->lock_comp_);
|
||||
}
|
||||
|
||||
void GarageDoor::set_button_sensor(sensor::Sensor *button_sensor) {
|
||||
this->button_sensor_ = button_sensor;
|
||||
this->button_sensor_->add_on_state_callback([this](float state) {
|
||||
// Expected values:
|
||||
// 0 = Disconnected
|
||||
// 133 = None
|
||||
// 434 = Light
|
||||
// 714 = Lock
|
||||
// 993 = Door
|
||||
LocalButton currentButton;
|
||||
if (state < 50) {
|
||||
currentButton = LOCAL_BUTTON_DISCONNECTED;
|
||||
} else if (state < 284) {
|
||||
currentButton = LOCAL_BUTTON_NONE;
|
||||
} else if (state < 574) {
|
||||
currentButton = LOCAL_BUTTON_LIGHT;
|
||||
} else if (state < 854) {
|
||||
currentButton = LOCAL_BUTTON_LOCK;
|
||||
} else {
|
||||
currentButton = LOCAL_BUTTON_DOOR;
|
||||
}
|
||||
|
||||
if (currentButton != this->last_local_button_) {
|
||||
this->last_local_button_ = currentButton;
|
||||
|
||||
switch (currentButton) {
|
||||
case LOCAL_BUTTON_DISCONNECTED:
|
||||
ESP_LOGD(TAG, "Local button is disconnected");
|
||||
ESP_LOGD(TAG, " Read Value: %u", state);
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_BUTTON_DISCONNECTED));
|
||||
break;
|
||||
case LOCAL_BUTTON_NONE:
|
||||
ESP_LOGD(TAG, "No local buttons currently pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", state);
|
||||
break;
|
||||
case LOCAL_BUTTON_LIGHT:
|
||||
ESP_LOGD(TAG, "Local light button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", state);
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_LOCAL_LIGHT));
|
||||
break;
|
||||
case LOCAL_BUTTON_LOCK:
|
||||
ESP_LOGD(TAG, "Local lock button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", state);
|
||||
if (this->internal_state_ == INTERNAL_STATE_LOCKED) {
|
||||
this->unlock_();
|
||||
} else {
|
||||
this->lock_();
|
||||
}
|
||||
break;
|
||||
case LOCAL_BUTTON_DOOR:
|
||||
ESP_LOGD(TAG, "Local door button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", state);
|
||||
this->handle_button_press_(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GarageDoor::set_closed_sensor(binary_sensor::BinarySensor *closed_sensor) {
|
||||
this->closed_sensor_ = closed_sensor;
|
||||
this->closed_sensor_->add_on_state_callback([this](bool state) {
|
||||
if (state) {
|
||||
ESP_LOGD(TAG, "Closed sensor is active");
|
||||
this->set_state_(PHYSICAL_STATE_CLOSED, INTERNAL_STATE_CLOSED);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Closed sensor is inactive");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GarageDoor::set_open_sensor(binary_sensor::BinarySensor *open_sensor) {
|
||||
this->open_sensor_ = open_sensor;
|
||||
this->open_sensor_->add_on_state_callback([this](bool state) {
|
||||
if (state) {
|
||||
ESP_LOGD(TAG, "Open sensor is active");
|
||||
this->set_state_(PHYSICAL_STATE_OPEN, INTERNAL_STATE_OPEN);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Open sensor is inactive");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GarageDoor::set_remote_sensor(binary_sensor::BinarySensor *remote_sensor) {
|
||||
this->remote_sensor_ = remote_sensor;
|
||||
this->remote_sensor_->add_on_state_callback([this](bool state) {
|
||||
ESP_LOGD(TAG, "Remote door button was pressed");
|
||||
if (state && this->internal_state_ != INTERNAL_STATE_LOCKED) {
|
||||
this->handle_button_press_(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GarageDoor::set_remote_light_sensor(binary_sensor::BinarySensor *remote_light_sensor) {
|
||||
this->remote_light_sensor_ = remote_light_sensor;
|
||||
this->remote_light_sensor_->add_on_state_callback([this](bool state) {
|
||||
ESP_LOGD(TAG, "Remote light button was pressed");
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_REMOTE_LIGHT));
|
||||
});
|
||||
}
|
||||
|
||||
void GarageDoor::set_warning_rtttl(rtttl::Rtttl *warning_rtttl) {
|
||||
this->warning_rtttl_ = warning_rtttl;
|
||||
this->warning_rtttl_->add_on_finished_playback_callback([this]() {
|
||||
this->activate_control_output_();
|
||||
});
|
||||
}
|
||||
|
||||
cover::CoverTraits GarageDoor::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void GarageDoor::setup() {
|
||||
if (this->closed_sensor_->state) {
|
||||
this->set_state_(PHYSICAL_STATE_CLOSED, INTERNAL_STATE_CLOSED, true);
|
||||
} else if (this->open_sensor_->state) {
|
||||
this->set_state_(PHYSICAL_STATE_OPEN, INTERNAL_STATE_OPEN, true);
|
||||
} else {
|
||||
this->set_state_(PHYSICAL_STATE_UNKNOWN, INTERNAL_STATE_UNKNOWN, true);
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::loop() {
|
||||
this->recompute_position_();
|
||||
this->ensure_target_state_();
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > DOOR_MOVING_PUBLISH_INTERVAL) {
|
||||
this->publish_state(false);
|
||||
this->last_publish_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::control(const cover::CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
this->target_state_ = TARGET_STATE_STOPPED;
|
||||
} else if (call.get_position().has_value()) {
|
||||
this->target_position_ = *call.get_position();
|
||||
if (this->target_position_ == COVER_CLOSED) {
|
||||
this->target_state_ = this->internal_state_ == INTERNAL_STATE_LOCKED ? TARGET_STATE_LOCKED : TARGET_STATE_CLOSED;
|
||||
} else if (this->target_position_ == COVER_OPEN) {
|
||||
this->target_state_ = TARGET_STATE_OPEN;
|
||||
} else {
|
||||
this->target_state_ = TARGET_STATE_POSITION;
|
||||
}
|
||||
|
||||
if (!this->is_at_target_position_() && this->target_position_ < this->position && this->current_operation != COVER_OPERATION_CLOSING) {
|
||||
this->warning_rtttl_->play(this->close_warning_tones_);
|
||||
this->set_state_(this->physical_state_, INTERNAL_STATE_CLOSE_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::lock_() {
|
||||
this->target_state_ = TARGET_STATE_LOCKED;
|
||||
}
|
||||
|
||||
void GarageDoor::unlock_() {
|
||||
if (this->internal_state_ == INTERNAL_STATE_LOCKED) {
|
||||
this->target_state_ = TARGET_STATE_CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::recompute_position_() {
|
||||
float direction;
|
||||
float normal_duration;
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_OPENING:
|
||||
direction = 1.0f;
|
||||
normal_duration = this->open_duration_;
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
direction = -1.0f;
|
||||
normal_duration = this->close_duration_;
|
||||
break;
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
if (this->closed_sensor_->state) {
|
||||
this->set_state_(PHYSICAL_STATE_CLOSED, INTERNAL_STATE_CLOSED);
|
||||
} else if (this->open_sensor_->state) {
|
||||
this->set_state_(PHYSICAL_STATE_OPEN, INTERNAL_STATE_OPEN);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
this->position += direction * (now - this->last_recompute_time_) / normal_duration;
|
||||
this->position = clamp(this->position, 0.01f, .99f);
|
||||
this->last_recompute_time_ = now;
|
||||
}
|
||||
|
||||
void GarageDoor::ensure_target_state_() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->control_output_state_) {
|
||||
if (now - this->control_output_state_change_time_ >= this->control_active_duration_) {
|
||||
this->control_output_->turn_off();
|
||||
this->control_output_state_ = false;
|
||||
this->control_output_state_change_time_ = now;
|
||||
}
|
||||
return;
|
||||
} else if (now - this->control_output_state_change_time_ < this->control_inactive_duration_) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->target_state_) {
|
||||
case TARGET_STATE_LOCKED:
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
this->set_state_(PHYSICAL_STATE_CLOSED, INTERNAL_STATE_LOCKED);
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_OPENING:
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->activate_control_output_();
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TARGET_STATE_CLOSED:
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
this->set_state_(PHYSICAL_STATE_CLOSED, INTERNAL_STATE_CLOSED);
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_OPENING:
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->activate_control_output_();
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TARGET_STATE_OPEN:
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
this->activate_control_output_();
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
this->warning_rtttl_->stop();
|
||||
if (this->physical_state_ == PHYSICAL_STATE_OPEN) {
|
||||
this->set_state_(PHYSICAL_STATE_OPEN, INTERNAL_STATE_OPEN);
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
} else {
|
||||
this->activate_control_output_();
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
case INTERNAL_STATE_OPENING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TARGET_STATE_STOPPED:
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_MOVING:
|
||||
case INTERNAL_STATE_OPENING:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
this->activate_control_output_();
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
this->warning_rtttl_->stop();
|
||||
if (this->physical_state_ == PHYSICAL_STATE_OPEN) {
|
||||
this->set_state_(PHYSICAL_STATE_OPEN, INTERNAL_STATE_OPEN);
|
||||
} else {
|
||||
this->set_state_(this->physical_state_, INTERNAL_STATE_STOPPED);
|
||||
}
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TARGET_STATE_POSITION:
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->activate_control_output_();
|
||||
break;
|
||||
case INTERNAL_STATE_OPENING:
|
||||
if (this->is_at_target_position_() || this->position > this->target_position_) {
|
||||
this->activate_control_output_();
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
if (!this->is_at_target_position_()) {
|
||||
this->activate_control_output_();
|
||||
} else {
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
if (this->is_at_target_position_()) {
|
||||
this->warning_rtttl_->stop();
|
||||
this->set_state_(this->physical_state_, INTERNAL_STATE_STOPPED);
|
||||
this->target_state_ = TARGET_STATE_NONE;
|
||||
} else if (this->position < this->target_position_) {
|
||||
this->warning_rtttl_->stop();
|
||||
this->activate_control_output_();
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
if (this->is_at_target_position_() || this->position < this->target_position_) {
|
||||
this->activate_control_output_();
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::activate_control_output_() {
|
||||
const uint32_t now = millis();
|
||||
this->control_output_->turn_on();
|
||||
this->control_output_state_ = true;
|
||||
this->control_output_state_change_time_ = now;
|
||||
this->last_recompute_time_ = now;
|
||||
switch (this->physical_state_) {
|
||||
case PHYSICAL_STATE_UNKNOWN:
|
||||
this->set_state_(PHYSICAL_STATE_MOVING, INTERNAL_STATE_MOVING);
|
||||
break;
|
||||
case PHYSICAL_STATE_MOVING:
|
||||
this->set_state_(PHYSICAL_STATE_UNKNOWN, INTERNAL_STATE_UNKNOWN);
|
||||
break;
|
||||
case PHYSICAL_STATE_CLOSED:
|
||||
this->set_state_(PHYSICAL_STATE_OPENING, INTERNAL_STATE_OPENING);
|
||||
break;
|
||||
case PHYSICAL_STATE_OPENING:
|
||||
if (this->reverses_on_stop_opening_) {
|
||||
this->set_state_(PHYSICAL_STATE_CLOSING, INTERNAL_STATE_CLOSING);
|
||||
} else {
|
||||
this->set_state_(PHYSICAL_STATE_STOPPED_OPENING, INTERNAL_STATE_STOPPED);
|
||||
}
|
||||
break;
|
||||
case PHYSICAL_STATE_STOPPED_OPENING:
|
||||
this->set_state_(PHYSICAL_STATE_CLOSING, INTERNAL_STATE_CLOSING);
|
||||
break;
|
||||
case PHYSICAL_STATE_OPEN:
|
||||
this->set_state_(PHYSICAL_STATE_CLOSING, INTERNAL_STATE_CLOSING);
|
||||
break;
|
||||
case PHYSICAL_STATE_CLOSING:
|
||||
if (this->reverses_on_stop_closing_) {
|
||||
this->set_state_(PHYSICAL_STATE_OPENING, INTERNAL_STATE_OPENING);
|
||||
} else {
|
||||
this->set_state_(PHYSICAL_STATE_STOPPED_CLOSING, INTERNAL_STATE_STOPPED);
|
||||
}
|
||||
break;
|
||||
case PHYSICAL_STATE_STOPPED_CLOSING:
|
||||
this->set_state_(PHYSICAL_STATE_OPENING, INTERNAL_STATE_OPENING);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::handle_button_press_(bool is_local) {
|
||||
switch (this->internal_state_) {
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->target_state_ = TARGET_STATE_CLOSED;
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
case INTERNAL_STATE_OPENING:
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
this->target_state_ = TARGET_STATE_STOPPED;
|
||||
break;
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
if (is_local) {
|
||||
this->target_state_ = TARGET_STATE_OPEN;
|
||||
}
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
this->target_state_ = TARGET_STATE_OPEN;
|
||||
break;
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
if (this->physical_state_ == PHYSICAL_STATE_STOPPED_OPENING) {
|
||||
this->target_state_ = TARGET_STATE_CLOSED;
|
||||
} else if (this->physical_state_ == PHYSICAL_STATE_STOPPED_CLOSING) {
|
||||
this->target_state_ = TARGET_STATE_OPEN;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoor::set_state_(PhysicalState physical_state, InternalState internal_state, bool is_initial_state) {
|
||||
if (is_initial_state) {
|
||||
ESP_LOGD(TAG, "Setting Initial Physical State: '%s'", physical_state_names[physical_state]);
|
||||
ESP_LOGD(TAG, "Setting Initial Internal State: '%s'", internal_state_names[internal_state]);
|
||||
} else if (this->physical_state_ != physical_state || this->internal_state_ != internal_state) {
|
||||
if (this->physical_state_ != physical_state) {
|
||||
ESP_LOGD(TAG, "Setting Physical State:");
|
||||
ESP_LOGD(TAG, " Current State: '%s'", physical_state_names[this->physical_state_]);
|
||||
ESP_LOGD(TAG, " New State: '%s'", physical_state_names[physical_state]);
|
||||
}
|
||||
if (this->internal_state_ != internal_state) {
|
||||
ESP_LOGD(TAG, "Setting Internal State:");
|
||||
ESP_LOGD(TAG, " Current State: '%s'", internal_state_names[this->internal_state_]);
|
||||
ESP_LOGD(TAG, " New State: '%s'", internal_state_names[internal_state]);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->physical_state_ != physical_state) {
|
||||
const uint32_t now = millis();
|
||||
if (this->last_open_time_sensor_ != nullptr && this->previous_physical_state_ == PHYSICAL_STATE_CLOSED && physical_state == PHYSICAL_STATE_OPEN) {
|
||||
uint32_t open_time = now - this->last_physical_state_change_time_;
|
||||
this->last_open_time_sensor_->publish_state(open_time);
|
||||
} else if (this->last_close_time_sensor_ != nullptr && this->previous_physical_state_ == PHYSICAL_STATE_OPEN && physical_state == PHYSICAL_STATE_CLOSED) {
|
||||
uint32_t close_time = now - this->last_physical_state_change_time_;
|
||||
this->last_close_time_sensor_->publish_state(close_time);
|
||||
}
|
||||
this->previous_physical_state_ = this->physical_state_;
|
||||
this->last_physical_state_change_time_ = now;
|
||||
this->physical_state_ = physical_state;
|
||||
}
|
||||
|
||||
this->internal_state_ = internal_state;
|
||||
|
||||
switch (internal_state) {
|
||||
case INTERNAL_STATE_UNKNOWN:
|
||||
this->position = 0.5f;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
break;
|
||||
case INTERNAL_STATE_MOVING:
|
||||
this->position = 0.5f;
|
||||
this->current_operation = COVER_OPERATION_CLOSING;
|
||||
break;
|
||||
case INTERNAL_STATE_LOCKED:
|
||||
case INTERNAL_STATE_CLOSED:
|
||||
this->position = COVER_CLOSED;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
break;
|
||||
case INTERNAL_STATE_OPENING:
|
||||
this->current_operation = COVER_OPERATION_OPENING;
|
||||
break;
|
||||
case INTERNAL_STATE_STOPPED:
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
break;
|
||||
case INTERNAL_STATE_OPEN:
|
||||
this->position = COVER_OPEN;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
break;
|
||||
case INTERNAL_STATE_CLOSE_WARNING:
|
||||
case INTERNAL_STATE_CLOSING:
|
||||
this->current_operation = COVER_OPERATION_CLOSING;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->publish_state(false);
|
||||
this->lock_comp_->publish_state(internal_state == INTERNAL_STATE_LOCKED ? LOCK_STATE_LOCKED : LOCK_STATE_UNLOCKED);
|
||||
}
|
||||
|
||||
std::string GarageDoor::get_event_(std::string event_type)
|
||||
{
|
||||
std::string event = "esphome.";
|
||||
event.append(this->get_object_id());
|
||||
event.append(".");
|
||||
event.append(event_type);
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
156
components/garage_door/garage_door.h
Normal file
156
components/garage_door/garage_door.h
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/api/custom_api_device.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/cover/cover.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/rtttl/rtttl.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
#define PHYSICAL_STATES \
|
||||
P(UNKNOWN) \
|
||||
P(MOVING) \
|
||||
P(CLOSED) \
|
||||
P(OPENING) \
|
||||
P(STOPPED_OPENING) \
|
||||
P(OPEN) \
|
||||
P(CLOSING) \
|
||||
P(STOPPED_CLOSING)
|
||||
#define P(x) PHYSICAL_STATE_##x,
|
||||
enum PhysicalState { PHYSICAL_STATES P };
|
||||
#undef P
|
||||
#define P(x) #x,
|
||||
const char * const physical_state_names[] = { PHYSICAL_STATES };
|
||||
|
||||
#define INTERNAL_STATES \
|
||||
I(UNKNOWN) \
|
||||
I(MOVING) \
|
||||
I(LOCKED) \
|
||||
I(CLOSED) \
|
||||
I(OPENING) \
|
||||
I(STOPPED) \
|
||||
I(OPEN) \
|
||||
I(CLOSE_WARNING) \
|
||||
I(CLOSING)
|
||||
#define I(x) INTERNAL_STATE_##x,
|
||||
enum InternalState { INTERNAL_STATES I };
|
||||
#undef I
|
||||
#define I(x) #x,
|
||||
const char * const internal_state_names[] = { INTERNAL_STATES };
|
||||
|
||||
enum TargetState {
|
||||
TARGET_STATE_NONE,
|
||||
TARGET_STATE_LOCKED,
|
||||
TARGET_STATE_CLOSED,
|
||||
TARGET_STATE_OPEN,
|
||||
TARGET_STATE_STOPPED,
|
||||
TARGET_STATE_POSITION,
|
||||
};
|
||||
|
||||
enum LocalButton {
|
||||
LOCAL_BUTTON_DISCONNECTED, // The button is not connected resulting in 0 volts at pin A0
|
||||
LOCAL_BUTTON_NONE,
|
||||
LOCAL_BUTTON_DOOR,
|
||||
LOCAL_BUTTON_LOCK,
|
||||
LOCAL_BUTTON_LIGHT
|
||||
};
|
||||
|
||||
class GarageDoor;
|
||||
|
||||
class GarageDoorLock : public lock::Lock, public Component
|
||||
{
|
||||
public:
|
||||
GarageDoorLock(GarageDoor *garage_door) : garage_door_(garage_door) {}
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override;
|
||||
void loop() override {}
|
||||
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override;
|
||||
|
||||
GarageDoor *garage_door_{nullptr};
|
||||
};
|
||||
|
||||
class GarageDoor : public cover::Cover, public Component, public api::CustomAPIDevice {
|
||||
public:
|
||||
GarageDoor();
|
||||
void set_name(const std::string &name) { Cover::set_name(name); this->lock_comp_->set_name(name); }
|
||||
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
|
||||
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
|
||||
void set_control_output(output::BinaryOutput *control_output) { this->control_output_ = control_output; }
|
||||
void set_button_sensor(sensor::Sensor *button_sensor);
|
||||
void set_closed_sensor(binary_sensor::BinarySensor *closed_sensor);
|
||||
void set_open_sensor(binary_sensor::BinarySensor *open_sensor);
|
||||
void set_remote_sensor(binary_sensor::BinarySensor *remote_sensor);
|
||||
void set_remote_light_sensor(binary_sensor::BinarySensor *remote_light_sensor);
|
||||
void set_warning_rtttl(rtttl::Rtttl *warning_rtttl);
|
||||
void set_close_warning_tones(const std::string &close_warning_tones) { this->close_warning_tones_ = close_warning_tones; }
|
||||
void set_control_active_duration(uint32_t control_active_duration) { this->control_active_duration_ = control_active_duration; }
|
||||
void set_control_inactive_duration(uint32_t control_inactive_duration) { this->control_inactive_duration_ = control_inactive_duration; }
|
||||
void set_reverses_on_stop_opening(bool reverses_on_stop_opening) { this->reverses_on_stop_opening_ = reverses_on_stop_opening; }
|
||||
void set_reverses_on_stop_closing(bool reverses_on_stop_closing) { this->reverses_on_stop_closing_ = reverses_on_stop_closing; }
|
||||
void set_last_open_time_sensor(sensor::Sensor *last_open_time_sensor) { this->last_open_time_sensor_ = last_open_time_sensor; }
|
||||
void set_last_close_time_sensor(sensor::Sensor *last_close_time_sensor) { this->last_close_time_sensor_ = last_close_time_sensor; }
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
cover::CoverTraits get_traits() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
friend GarageDoorLock;
|
||||
GarageDoorLock *lock_comp_;
|
||||
|
||||
uint32_t open_duration_;
|
||||
uint32_t close_duration_;
|
||||
output::BinaryOutput *control_output_;
|
||||
sensor::Sensor *button_sensor_;
|
||||
binary_sensor::BinarySensor *closed_sensor_;
|
||||
binary_sensor::BinarySensor *open_sensor_;
|
||||
binary_sensor::BinarySensor *remote_sensor_;
|
||||
binary_sensor::BinarySensor *remote_light_sensor_;
|
||||
rtttl::Rtttl *warning_rtttl_;
|
||||
std::string close_warning_tones_;
|
||||
uint32_t control_active_duration_;
|
||||
uint32_t control_inactive_duration_;
|
||||
bool reverses_on_stop_opening_{false};
|
||||
bool reverses_on_stop_closing_{false};
|
||||
sensor::Sensor *last_open_time_sensor_{nullptr};
|
||||
sensor::Sensor *last_close_time_sensor_{nullptr};
|
||||
|
||||
PhysicalState previous_physical_state_{PHYSICAL_STATE_UNKNOWN};
|
||||
PhysicalState physical_state_{PHYSICAL_STATE_UNKNOWN};
|
||||
uint32_t last_physical_state_change_time_{0};
|
||||
InternalState internal_state_{INTERNAL_STATE_UNKNOWN};
|
||||
TargetState target_state_{TARGET_STATE_NONE};
|
||||
float target_position_{0};
|
||||
LocalButton last_local_button_{LOCAL_BUTTON_NONE};
|
||||
uint32_t last_recompute_time_{0};
|
||||
bool control_output_state_{false};
|
||||
uint32_t control_output_state_change_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
|
||||
void control(const cover::CoverCall &call) override;
|
||||
|
||||
void lock_();
|
||||
void unlock_();
|
||||
|
||||
void recompute_position_();
|
||||
void ensure_target_state_();
|
||||
|
||||
bool is_at_target_position_() { return (this->target_position_ - 0.05f) <= this->position && this->position <= (this->target_position_ + 0.05f); }
|
||||
void activate_control_output_();
|
||||
void handle_button_press_(bool is_local);
|
||||
void set_state_(PhysicalState physical_state, InternalState internal_state, bool is_initial_state = false);
|
||||
std::string get_event_(std::string event_type);
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
885
custom/GarageDoor.h
Normal file
885
custom/GarageDoor.h
Normal file
@ -0,0 +1,885 @@
|
||||
#include "esphome.h"
|
||||
|
||||
using namespace esphome;
|
||||
|
||||
static const char* TAG = "NuttyGarageDoor";
|
||||
|
||||
const bool HAS_INTERNAL_CLOSED_SENSOR = true;
|
||||
const bool HAS_INTERNAL_OPEN_SENSOR = true;
|
||||
|
||||
// Open/Close durations used to determine position while opening/closing
|
||||
const uint32_t NORMAL_OPEN_DURATION = 13000;
|
||||
const uint32_t NORMAL_CLOSE_DURATION = 12000;
|
||||
|
||||
// Minimum number of milliseconds to keep the control pin active/inactive when changing states
|
||||
const uint32_t CONTROL_PIN_ACTIVE_DURATION = 200;
|
||||
const uint32_t CONTROL_PIN_INACTIVE_DURATION = 200;
|
||||
|
||||
// Number of milliseconds between publishing the state while the door is opening or closing
|
||||
const uint32_t DOOR_MOVING_PUBLISH_INTERVAL = 750;
|
||||
|
||||
// Number of milliseconds between reads of the ADC to get local button state, this is needed to prevent wifi issues from reading to frequently
|
||||
const uint32_t LOCAL_BUTTON_READ_INTERVAL = 75;
|
||||
|
||||
// Number of milliseconds to wait before checking the open/close sensors after starting to open/close the door to prevent false "failed" triggers
|
||||
const uint32_t SENSOR_READ_DELAY = 1000;
|
||||
|
||||
// Close warning
|
||||
const std::string CLOSE_WARNING_RTTTL = "Imperial:d=4, o=5, b=100:e, e, e, 8c, 16p, 16g, e, 8c, 16p, 16g, e, p, b, b, b, 8c6, 16p, 16g, d#, 8c, 16p, 16g, e, 8p";
|
||||
|
||||
const uint8_t PIN_CONTROL_RELAY = D1;
|
||||
const uint8_t PIN_CLOSED_SENSOR = D2;
|
||||
const uint8_t PIN_OPEN_SENSOR = D7;
|
||||
const uint8_t PIN_REMOTE_BUTTON = D5;
|
||||
const uint8_t PIN_REMOTE_LIGHT_BUTTON = D6;
|
||||
const uint8_t PIN_STATUS_LED = D4;
|
||||
const uint8_t PIN_CLOSE_WARNING_BUZZER = D8;
|
||||
|
||||
// Events
|
||||
const std::string EVENT_LOCAL_LIGHT = "local_light_button";
|
||||
const std::string EVENT_REMOTE_LIGHT = "remote_light_button";
|
||||
const std::string EVENT_FAILED_OPEN = "open_failed";
|
||||
const std::string EVENT_FAILED_CLOSE = "close_failed";
|
||||
const std::string EVENT_BUTTON_DISCONNECTED = "button_disconnected";
|
||||
|
||||
// Lock Binary sensor state aliases
|
||||
const bool LOCK_STATE_LOCKED = false;
|
||||
const bool LOCK_STATE_UNLOCKED = true;
|
||||
|
||||
enum StateChangeType : uint8_t {
|
||||
STATE_CHANGE_INTERNAL = 0,
|
||||
STATE_CHANGE_BUTTON,
|
||||
STATE_CHANGE_CLOSE_SENSOR,
|
||||
STATE_CHANGE_OPEN_SENSOR,
|
||||
STATE_CHANGE_CANCEL_WARNING
|
||||
};
|
||||
|
||||
enum InternalState : uint8_t {
|
||||
// On startup the state is unknown
|
||||
STATE_UNKNOWN = 0,
|
||||
// The door is moving from an unknown state so which direction it is moving is unknown
|
||||
STATE_MOVING,
|
||||
// The door is "locked" which is the same as closed but will not open for remotes (but local buttons and Home Assistant can open or unlock it)
|
||||
STATE_LOCKED,
|
||||
// The door is closed
|
||||
STATE_CLOSED,
|
||||
// The door is opening
|
||||
STATE_OPENING,
|
||||
// The door was stopped while it was opening
|
||||
STATE_STOPPED_OPENING,
|
||||
// The door is open
|
||||
STATE_OPEN,
|
||||
// The door is currently at least partially open but was requested closed by Home Assistant so we are waiting to close after an alert (beeper)
|
||||
STATE_CLOSE_WARNING,
|
||||
// The door is closing
|
||||
STATE_CLOSING,
|
||||
// The door was stopped while it was closing
|
||||
STATE_STOPPED_CLOSING,
|
||||
|
||||
// Special states that the door will never actually be in but are used as target states
|
||||
// Stopped where ever it currently is at
|
||||
STATE_STOPPED,
|
||||
// Stopped at a specific position
|
||||
STATE_POSITION,
|
||||
// No currently requested state
|
||||
STATE_NONE
|
||||
};
|
||||
|
||||
enum LocalButton : uint8_t {
|
||||
LOCAL_BUTTON_DISCONNECTED = 0, // The button is not connected resulting in 0 volts at pin A0
|
||||
LOCAL_BUTTON_NONE,
|
||||
LOCAL_BUTTON_DOOR,
|
||||
LOCAL_BUTTON_LOCK,
|
||||
LOCAL_BUTTON_LIGHT
|
||||
};
|
||||
|
||||
class GarageDoorLock : public binary_sensor::BinarySensor, public Component
|
||||
{
|
||||
public:
|
||||
GarageDoorLock() {}
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override {}
|
||||
void loop() override {}
|
||||
};
|
||||
|
||||
class GarageDoorCover : public cover::Cover, public Component, public api::CustomAPIDevice
|
||||
{
|
||||
public:
|
||||
GarageDoorCover();
|
||||
void set_rtttl_buzzer(rtttl::Rtttl *buzzer) { this->buzzer_ = buzzer; }
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override;
|
||||
cover::CoverTraits get_traits() override;
|
||||
void control(const cover::CoverCall &call) override;
|
||||
void loop() override;
|
||||
GarageDoorLock *get_lock_sensor() { return this->lock_sensor_; }
|
||||
|
||||
protected:
|
||||
rtttl::Rtttl *buzzer_;
|
||||
esphome::esp8266::ESP8266GPIOPin *control_pin_;
|
||||
esphome::esp8266::ESP8266GPIOPin *closed_pin_;
|
||||
esphome::esp8266::ESP8266GPIOPin *open_pin_;
|
||||
esphome::esp8266::ESP8266GPIOPin *remote_pin_;
|
||||
esphome::esp8266::ESP8266GPIOPin *remote_light_pin_;
|
||||
GarageDoorLock *lock_sensor_;
|
||||
|
||||
InternalState internal_state_{STATE_UNKNOWN};
|
||||
InternalState target_state_{STATE_NONE};
|
||||
float target_position_{0};
|
||||
uint32_t control_pin_active_time_{0};
|
||||
uint32_t control_pin_inactive_time_{0};
|
||||
uint32_t last_state_change_time_{0};
|
||||
uint32_t last_position_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
uint32_t last_local_button_read_time_{0};
|
||||
LocalButton last_local_button_{LOCAL_BUTTON_NONE};
|
||||
bool last_remote_state_{false};
|
||||
bool last_remote_light_state_{false};
|
||||
|
||||
void lock_();
|
||||
void unlock_();
|
||||
|
||||
bool check_control_pin_();
|
||||
bool check_for_closed_position_();
|
||||
bool check_for_open_position_();
|
||||
bool check_for_position_update_();
|
||||
bool ensure_target_state_();
|
||||
bool check_local_buttons_();
|
||||
bool check_remote_buttons_();
|
||||
void change_to_next_state_(StateChangeType change_type = STATE_CHANGE_INTERNAL);
|
||||
void set_internal_state_(InternalState state, bool is_initial_state = false);
|
||||
InternalState get_internal_state_() { return this->internal_state_; }
|
||||
std::string get_event_(std::string event_type);
|
||||
const char *internal_state_to_str_(InternalState state);
|
||||
};
|
||||
|
||||
GarageDoorCover::GarageDoorCover()
|
||||
{
|
||||
this->set_device_class("garage");
|
||||
this->control_pin_ = new esphome::esp8266::ESP8266GPIOPin();
|
||||
this->control_pin_->set_pin(PIN_CONTROL_RELAY);
|
||||
this->control_pin_->set_inverted(false);
|
||||
this->control_pin_->set_flags(gpio::Flags::FLAG_OUTPUT);
|
||||
this->closed_pin_ = new esphome::esp8266::ESP8266GPIOPin();
|
||||
this->closed_pin_->set_pin(PIN_CLOSED_SENSOR);
|
||||
this->closed_pin_->set_inverted(true);
|
||||
this->closed_pin_->set_flags(gpio::Flags::FLAG_PULLUP);
|
||||
this->open_pin_ = new esphome::esp8266::ESP8266GPIOPin();
|
||||
this->open_pin_->set_pin(PIN_OPEN_SENSOR);
|
||||
this->open_pin_->set_inverted(true);
|
||||
this->open_pin_->set_flags(gpio::Flags::FLAG_PULLUP);
|
||||
this->remote_pin_ = new esphome::esp8266::ESP8266GPIOPin();
|
||||
this->remote_pin_->set_pin(PIN_REMOTE_BUTTON);
|
||||
this->remote_pin_->set_inverted(true);
|
||||
this->remote_pin_->set_flags(gpio::Flags::FLAG_PULLUP);
|
||||
this->remote_light_pin_ = new esphome::esp8266::ESP8266GPIOPin();
|
||||
this->remote_light_pin_->set_pin(PIN_REMOTE_LIGHT_BUTTON);
|
||||
this->remote_light_pin_->set_inverted(true);
|
||||
this->remote_light_pin_->set_flags(gpio::Flags::FLAG_PULLUP);
|
||||
this->lock_sensor_ = new GarageDoorLock();
|
||||
}
|
||||
|
||||
void GarageDoorCover::setup()
|
||||
{
|
||||
this->control_pin_->setup();
|
||||
this->closed_pin_->setup();
|
||||
this->open_pin_->setup();
|
||||
this->remote_pin_->setup();
|
||||
this->remote_light_pin_->setup();
|
||||
|
||||
this->lock_sensor_->publish_initial_state(LOCK_STATE_UNLOCKED);
|
||||
|
||||
if (this->closed_pin_->digital_read())
|
||||
{
|
||||
this->set_internal_state_(STATE_CLOSED, true);
|
||||
}
|
||||
else if (this->open_pin_->digital_read())
|
||||
{
|
||||
this->set_internal_state_(STATE_OPEN, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set_internal_state_(STATE_UNKNOWN, true);
|
||||
}
|
||||
|
||||
this->register_service(&GarageDoorCover::lock_, "lock");
|
||||
this->register_service(&GarageDoorCover::unlock_, "unlock");
|
||||
}
|
||||
|
||||
cover::CoverTraits GarageDoorCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_is_assumed_state(false);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void GarageDoorCover::control(const cover::CoverCall &call)
|
||||
{
|
||||
this->change_to_next_state_(STATE_CHANGE_CANCEL_WARNING);
|
||||
|
||||
if (call.get_stop())
|
||||
{
|
||||
this->target_state_ = STATE_STOPPED;
|
||||
}
|
||||
else if (call.get_position().has_value())
|
||||
{
|
||||
this->target_position_ = *call.get_position();
|
||||
if (this->target_position_ == this->position && this->current_operation == COVER_OPERATION_IDLE && this->get_internal_state_() != STATE_UNKNOWN)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (this->target_position_ == COVER_CLOSED)
|
||||
{
|
||||
this->target_state_ = this->get_internal_state_() == STATE_LOCKED ? STATE_LOCKED : STATE_CLOSED;
|
||||
}
|
||||
else if (this->target_position_ == COVER_OPEN)
|
||||
{
|
||||
this->target_state_ = STATE_OPEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->target_state_ = STATE_POSITION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoorCover::loop()
|
||||
{
|
||||
if (this->check_control_pin_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->check_for_closed_position_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->check_for_open_position_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->check_for_position_update_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->ensure_target_state_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->check_local_buttons_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->check_remote_buttons_())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoorCover::lock_()
|
||||
{
|
||||
if (this->get_internal_state_() != STATE_LOCKED)
|
||||
{
|
||||
this->target_state_ = STATE_LOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoorCover::unlock_()
|
||||
{
|
||||
if (this->get_internal_state_() == STATE_LOCKED)
|
||||
{
|
||||
this->target_state_ = STATE_CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_control_pin_()
|
||||
{
|
||||
if (this->control_pin_active_time_ > 0)
|
||||
{
|
||||
const uint32_t now = millis();
|
||||
if (now - this->control_pin_active_time_ >= CONTROL_PIN_ACTIVE_DURATION)
|
||||
{
|
||||
this->control_pin_->digital_write(false);
|
||||
this->control_pin_active_time_ = 0;
|
||||
this->control_pin_inactive_time_ = now;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_for_closed_position_()
|
||||
{
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && (millis() - this->last_state_change_time_) >= SENSOR_READ_DELAY && this->closed_pin_->digital_read())
|
||||
{
|
||||
ESP_LOGD(TAG, "Door closed sensor is active");
|
||||
|
||||
if (this->get_internal_state_() == STATE_OPENING)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_FAILED_OPEN));
|
||||
}
|
||||
|
||||
this->change_to_next_state_(STATE_CHANGE_CLOSE_SENSOR);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_for_open_position_()
|
||||
{
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && (millis() - this->last_state_change_time_) >= SENSOR_READ_DELAY && this->open_pin_->digital_read())
|
||||
{
|
||||
ESP_LOGD(TAG, "Door open sensor is active");
|
||||
|
||||
if (this->get_internal_state_() == STATE_CLOSING)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_FAILED_CLOSE));
|
||||
}
|
||||
|
||||
this->change_to_next_state_(STATE_CHANGE_OPEN_SENSOR);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_for_position_update_()
|
||||
{
|
||||
float direction;
|
||||
uint32_t normal_duration;
|
||||
switch (this->get_internal_state_())
|
||||
{
|
||||
case STATE_OPENING:
|
||||
direction = 1.0f;
|
||||
normal_duration = NORMAL_OPEN_DURATION;
|
||||
break;
|
||||
|
||||
case STATE_CLOSING:
|
||||
direction = -1.0f;
|
||||
normal_duration = NORMAL_CLOSE_DURATION;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
const uint32_t current_duration = now - this->last_state_change_time_;
|
||||
if (current_duration > normal_duration * 2)
|
||||
{
|
||||
// This should never happen but if it does we go into an unknown state
|
||||
this->set_internal_state_(STATE_UNKNOWN);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->position = clamp(this->position + (direction * (now - this->last_position_time_) / normal_duration), 0.01f, 0.99f);
|
||||
this->last_position_time_ = now;
|
||||
if (this->target_state_ == STATE_POSITION && this->position == this->target_position_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (now - this->last_publish_time_ >= DOOR_MOVING_PUBLISH_INTERVAL)
|
||||
{
|
||||
this->publish_state(false);
|
||||
this->last_publish_time_ = now;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::ensure_target_state_()
|
||||
{
|
||||
if (!this->buzzer_->is_playing() && this->target_state_ != STATE_NONE)
|
||||
{
|
||||
InternalState current_state = this->get_internal_state_();
|
||||
switch (this->target_state_)
|
||||
{
|
||||
case STATE_LOCKED:
|
||||
if (current_state == STATE_LOCKED)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
return false;
|
||||
}
|
||||
if (current_state == STATE_MOVING || current_state == STATE_CLOSING)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (current_state == STATE_CLOSED)
|
||||
{
|
||||
this->set_internal_state_(STATE_LOCKED);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
return true;
|
||||
}
|
||||
|
||||
case STATE_CLOSED:
|
||||
if (current_state == STATE_CLOSED)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
return false;
|
||||
}
|
||||
if (current_state == STATE_MOVING || current_state == STATE_CLOSING)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (current_state == STATE_LOCKED)
|
||||
{
|
||||
this->set_internal_state_(STATE_CLOSED);
|
||||
this->target_state_ = STATE_NONE;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
return true;
|
||||
}
|
||||
|
||||
case STATE_OPEN:
|
||||
if (current_state == STATE_OPEN)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
return false;
|
||||
}
|
||||
if (current_state == STATE_MOVING || current_state == STATE_OPENING)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
return true;
|
||||
}
|
||||
|
||||
case STATE_POSITION:
|
||||
if (current_state == STATE_MOVING)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (this->position < this->target_position_ && current_state != STATE_OPENING)
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
return true;
|
||||
}
|
||||
else if (this->position > this->target_position_ && current_state != STATE_CLOSING)
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
return true;
|
||||
}
|
||||
else if (this->position == this->target_position_ && (current_state == STATE_OPENING || current_state == STATE_CLOSING))
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
this->target_state_ = STATE_NONE;
|
||||
return true;
|
||||
}
|
||||
else if (this->position == this->target_position_)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
case STATE_STOPPED:
|
||||
if (this->current_operation != COVER_OPERATION_IDLE)
|
||||
{
|
||||
this->change_to_next_state_();
|
||||
this->target_state_ = STATE_NONE;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_local_buttons_()
|
||||
{
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_local_button_read_time_ > LOCAL_BUTTON_READ_INTERVAL)
|
||||
{
|
||||
this->last_local_button_read_time_ = now;
|
||||
|
||||
int buttonValue = analogRead(A0);
|
||||
|
||||
// Expected values:
|
||||
// 0 = Disconnected
|
||||
// 133 = None
|
||||
// 434 = Light
|
||||
// 714 = Lock
|
||||
// 993 = Door
|
||||
|
||||
LocalButton currentButton;
|
||||
if (buttonValue < 50)
|
||||
{
|
||||
currentButton = LOCAL_BUTTON_DISCONNECTED;
|
||||
}
|
||||
else if (buttonValue < 284)
|
||||
{
|
||||
currentButton = LOCAL_BUTTON_NONE;
|
||||
}
|
||||
else if (buttonValue < 574)
|
||||
{
|
||||
currentButton = LOCAL_BUTTON_LIGHT;
|
||||
}
|
||||
else if (buttonValue < 854)
|
||||
{
|
||||
currentButton = LOCAL_BUTTON_LOCK;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentButton = LOCAL_BUTTON_DOOR;
|
||||
}
|
||||
|
||||
if (currentButton != this->last_local_button_)
|
||||
{
|
||||
this->last_local_button_ = currentButton;
|
||||
|
||||
switch (currentButton)
|
||||
{
|
||||
case LOCAL_BUTTON_DISCONNECTED:
|
||||
ESP_LOGD(TAG, "Local button is disconnected");
|
||||
ESP_LOGD(TAG, " Read Value: %u", buttonValue);
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_BUTTON_DISCONNECTED));
|
||||
break;
|
||||
|
||||
case LOCAL_BUTTON_NONE:
|
||||
ESP_LOGD(TAG, "No local buttons currently pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", buttonValue);
|
||||
break;
|
||||
|
||||
case LOCAL_BUTTON_LIGHT:
|
||||
ESP_LOGD(TAG, "Local light button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", buttonValue);
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_LOCAL_LIGHT));
|
||||
break;
|
||||
|
||||
case LOCAL_BUTTON_LOCK:
|
||||
ESP_LOGD(TAG, "Local lock button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", buttonValue);
|
||||
if (this->get_internal_state_() == STATE_LOCKED)
|
||||
{
|
||||
this->unlock_();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->lock_();
|
||||
}
|
||||
break;
|
||||
|
||||
case LOCAL_BUTTON_DOOR:
|
||||
ESP_LOGD(TAG, "Local door button was pressed");
|
||||
ESP_LOGD(TAG, " Read Value: %u", buttonValue);
|
||||
this->change_to_next_state_(STATE_CHANGE_BUTTON);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GarageDoorCover::check_remote_buttons_()
|
||||
{
|
||||
bool currentRemoteState = this->remote_pin_->digital_read();
|
||||
if (currentRemoteState != this->last_remote_state_)
|
||||
{
|
||||
last_remote_state_ = currentRemoteState;
|
||||
if (currentRemoteState && this->get_internal_state_() != STATE_LOCKED)
|
||||
{
|
||||
ESP_LOGD(TAG, "Remote door button was pressed");
|
||||
this->change_to_next_state_(STATE_CHANGE_BUTTON);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool currentRemoteLightState = this->remote_light_pin_->digital_read();
|
||||
if (currentRemoteLightState != this->last_remote_light_state_)
|
||||
{
|
||||
last_remote_light_state_ = currentRemoteLightState;
|
||||
if (currentRemoteLightState)
|
||||
{
|
||||
ESP_LOGD(TAG, "Remote light button was pressed");
|
||||
this->fire_homeassistant_event(this->get_event_(EVENT_REMOTE_LIGHT));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GarageDoorCover::change_to_next_state_(StateChangeType change_type)
|
||||
{
|
||||
if (millis() < this->control_pin_inactive_time_ + CONTROL_PIN_INACTIVE_DURATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (change_type)
|
||||
{
|
||||
case STATE_CHANGE_CLOSE_SENSOR:
|
||||
this->set_internal_state_(STATE_CLOSED);
|
||||
if (HAS_INTERNAL_CLOSED_SENSOR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_CHANGE_OPEN_SENSOR:
|
||||
this->set_internal_state_(STATE_OPEN);
|
||||
if (HAS_INTERNAL_OPEN_SENSOR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_CHANGE_CANCEL_WARNING:
|
||||
if (this->get_internal_state_() == STATE_CLOSE_WARNING)
|
||||
{
|
||||
this->buzzer_->stop();
|
||||
this->set_internal_state_(this->position == COVER_OPEN ? STATE_OPEN : STATE_STOPPED_OPENING);
|
||||
this->target_state_ = STATE_NONE;
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
if (change_type == STATE_CHANGE_BUTTON)
|
||||
{
|
||||
this->target_state_ = STATE_NONE;
|
||||
}
|
||||
|
||||
switch (this->get_internal_state_())
|
||||
{
|
||||
case STATE_UNKNOWN:
|
||||
this->set_internal_state_(STATE_MOVING);
|
||||
break;
|
||||
|
||||
case STATE_MOVING:
|
||||
this->set_internal_state_(STATE_UNKNOWN);
|
||||
break;
|
||||
|
||||
case STATE_LOCKED:
|
||||
this->set_internal_state_(STATE_OPENING);
|
||||
break;
|
||||
|
||||
case STATE_CLOSED:
|
||||
this->set_internal_state_(STATE_OPENING);
|
||||
break;
|
||||
|
||||
case STATE_OPENING:
|
||||
this->set_internal_state_(STATE_STOPPED_OPENING);
|
||||
break;
|
||||
|
||||
case STATE_STOPPED_OPENING:
|
||||
if (change_type == STATE_CHANGE_BUTTON || this->position > this->target_position_)
|
||||
{
|
||||
this->set_internal_state_(STATE_CLOSING);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set_internal_state_(STATE_CLOSE_WARNING);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_OPEN:
|
||||
this->set_internal_state_(change_type == STATE_CHANGE_BUTTON ? STATE_CLOSING : STATE_CLOSE_WARNING);
|
||||
break;
|
||||
|
||||
case STATE_CLOSE_WARNING:
|
||||
if (change_type == STATE_CHANGE_BUTTON)
|
||||
{
|
||||
this->buzzer_->stop();
|
||||
this->set_internal_state_(this->position == COVER_OPEN ? STATE_OPEN : STATE_STOPPED_OPENING);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set_internal_state_(STATE_CLOSING);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_CLOSING:
|
||||
this->set_internal_state_(STATE_STOPPED_CLOSING);
|
||||
break;
|
||||
|
||||
case STATE_STOPPED_CLOSING:
|
||||
this->set_internal_state_(STATE_OPENING);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->get_internal_state_() == STATE_CLOSE_WARNING)
|
||||
{
|
||||
this->buzzer_->play(CLOSE_WARNING_RTTTL);
|
||||
}
|
||||
else
|
||||
{
|
||||
// "Press" the control "button"
|
||||
this->control_pin_->digital_write(true);
|
||||
this->control_pin_active_time_ = millis();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GarageDoorCover::set_internal_state_(InternalState state, bool is_initial_state)
|
||||
{
|
||||
if (is_initial_state)
|
||||
{
|
||||
ESP_LOGD(TAG, "Setting Initial Internal State: '%s'", this->internal_state_to_str_(state));
|
||||
}
|
||||
else if (this->internal_state_ != state)
|
||||
{
|
||||
ESP_LOGD(TAG, "Setting Internal State:");
|
||||
ESP_LOGD(TAG, " Current State: '%s'", this->internal_state_to_str_(this->get_internal_state_()));
|
||||
ESP_LOGD(TAG, " New State: '%s'", this->internal_state_to_str_(state));
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->internal_state_ = state;
|
||||
|
||||
if (state == STATE_UNKNOWN)
|
||||
{
|
||||
this->position = 0.5f;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
}
|
||||
else if (state == STATE_MOVING)
|
||||
{
|
||||
this->position = 0.5f;
|
||||
this->current_operation = COVER_OPERATION_CLOSING;
|
||||
}
|
||||
else if (state == STATE_LOCKED || state == STATE_CLOSED)
|
||||
{
|
||||
this->position = COVER_CLOSED;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
}
|
||||
else if (state == STATE_OPENING)
|
||||
{
|
||||
this->current_operation = COVER_OPERATION_OPENING;
|
||||
}
|
||||
else if (state == STATE_STOPPED_OPENING || state == STATE_CLOSE_WARNING || state == STATE_STOPPED_CLOSING)
|
||||
{
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
}
|
||||
else if (state == STATE_OPEN)
|
||||
{
|
||||
this->position = COVER_OPEN;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
}
|
||||
else if (state == STATE_CLOSING)
|
||||
{
|
||||
this->current_operation = COVER_OPERATION_CLOSING;
|
||||
}
|
||||
|
||||
this->publish_state(false);
|
||||
if (is_initial_state)
|
||||
{
|
||||
this->lock_sensor_->publish_initial_state(state == STATE_LOCKED ? LOCK_STATE_LOCKED : LOCK_STATE_UNLOCKED);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->lock_sensor_->publish_state(state == STATE_LOCKED ? LOCK_STATE_LOCKED : LOCK_STATE_UNLOCKED);
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
this->last_state_change_time_ = now;
|
||||
this->last_position_time_ = now;
|
||||
}
|
||||
|
||||
std::string GarageDoorCover::get_event_(std::string event_type)
|
||||
{
|
||||
std::string event = "esphome.";
|
||||
event.append(this->get_object_id());
|
||||
event.append(".");
|
||||
event.append(event_type);
|
||||
return event;
|
||||
}
|
||||
|
||||
const char *GarageDoorCover::internal_state_to_str_(InternalState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case STATE_UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
|
||||
case STATE_MOVING:
|
||||
return "MOVING";
|
||||
|
||||
case STATE_LOCKED:
|
||||
return "LOCKED";
|
||||
|
||||
case STATE_CLOSED:
|
||||
return "CLOSED";
|
||||
|
||||
case STATE_OPENING:
|
||||
return "OPENING";
|
||||
|
||||
case STATE_STOPPED_OPENING:
|
||||
return "STOPPED_OPENING";
|
||||
|
||||
case STATE_OPEN:
|
||||
return "OPEN";
|
||||
|
||||
case STATE_CLOSE_WARNING:
|
||||
return "CLOSE_WARNING";
|
||||
|
||||
case STATE_CLOSING:
|
||||
return "CLOSING";
|
||||
|
||||
case STATE_STOPPED_CLOSING:
|
||||
return "STOPPED_CLOSING";
|
||||
|
||||
default:
|
||||
return "INTERNAL_STATE_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
GarageDoorCover *GarageDoor = new GarageDoorCover();
|
107
devices/second-garage-door.yaml
Normal file
107
devices/second-garage-door.yaml
Normal file
@ -0,0 +1,107 @@
|
||||
substitutions:
|
||||
device_id: second-garage-door
|
||||
device_name: Second Garage Door
|
||||
board: d1_mini
|
||||
ip_address: !secret second-garage-door-ip
|
||||
ota_pwd: !secret second-garage-door-ota-pwd
|
||||
api_pwd: !secret second-garage-door-api-pwd
|
||||
ap_wifi_pwd: !secret second-garage-door-ap-pwd
|
||||
log_level: debug
|
||||
|
||||
packages:
|
||||
device_base: !include ../packages/device_base_esp8266.yaml
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ../components
|
||||
components: [ garage_door ]
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: ${device_name} Closed Sensor
|
||||
id: closed_in
|
||||
entity_category: diagnostic
|
||||
pin:
|
||||
number: D2
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
filters:
|
||||
- delayed_on: 50ms
|
||||
- delayed_off: 50ms
|
||||
- platform: gpio
|
||||
name: ${device_name} Open Sensor
|
||||
id: open_in
|
||||
entity_category: diagnostic
|
||||
pin:
|
||||
number: D7
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
filters:
|
||||
- delayed_on: 50ms
|
||||
- delayed_off: 50ms
|
||||
- platform: gpio
|
||||
id: remote_in
|
||||
pin:
|
||||
number: D5
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
- platform: gpio
|
||||
id: remote_light_in
|
||||
pin:
|
||||
number: D6
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
|
||||
cover:
|
||||
- platform: garage_door
|
||||
name: ${device_name}
|
||||
open_duration: 12947ms
|
||||
close_duration: 12875ms
|
||||
control_output: control_out
|
||||
button_sensor: button_in
|
||||
closed_sensor: closed_in
|
||||
open_sensor: open_in
|
||||
remote_sensor: remote_in
|
||||
remote_light_sensor: remote_light_in
|
||||
warning_rtttl: warning_rtttl
|
||||
last_open_time_sensor:
|
||||
name: ${device_name} Last Open Time
|
||||
last_close_time_sensor:
|
||||
name: ${device_name} Last Close Time
|
||||
|
||||
logger:
|
||||
logs:
|
||||
sensor: NONE
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: control_out
|
||||
pin: D1
|
||||
- platform: esp8266_pwm
|
||||
id: rtttl_out
|
||||
pin: D8
|
||||
|
||||
rtttl:
|
||||
id: warning_rtttl
|
||||
output: rtttl_out
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: button_in
|
||||
pin: A0
|
||||
raw: true
|
||||
update_interval: 75ms
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
number: D4
|
||||
inverted: false
|
BIN
documents/Garage Door Controller/Schematic.pdf
Normal file
BIN
documents/Garage Door Controller/Schematic.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user