Make storing persistent state the resposibility of ratgdo_number (#30)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Marius Muja 2023-07-07 15:56:12 -07:00 committed by GitHub
parent 3721bb5465
commit bd36a7e6a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 50 deletions

View File

@ -22,6 +22,10 @@ The ESPHome firmware will allow you to open the door to any position after calib
<img width="560" alt="position_demo" src="https://github.com/ESPHome-RATGDO/esphome-ratgdo/assets/663432/22a9873e-67bb-4b2f-bb32-70047cfe666d">
## Updating from versions older than 2023.07.07
When updating from older versions, save the rolling counter value and restore it via the number entity after flashing the new firmware. If you forget to save the code, check the Home Assistant history.
# ESPHome config
- [ESPHome config for v2 board with ESP8266 D1 Mini lite](https://github.com/ESPHome-RATGDO/esphome-ratgdo/blob/main/static/v2board_esp8266_d1_mini_lite.yaml)

View File

@ -15,6 +15,14 @@ ratgdo:
output_gdo_pin: ${uart_tx_pin}
input_obst_pin: ${input_obst_pin}
remote_id: 0x539
on_sync_failed:
then:
- homeassistant.service:
service: persistent_notification.create
data:
title: "${friendly_name} sync failed"
message: "Failed to communicate with garage opener on startup; Check the ${friendly_name} Rolling code counter number entity history and set the entity to one number larger than the largest value in history. [ESPHome devices](/config/devices/dashboard?domain=esphome)"
notification_id: "esphome_ratgdo_${id_prefix}_sync_failed"
sensor:
- platform: ratgdo

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ID
from esphome import automation, pins
from esphome.const import CONF_ID, CONF_TRIGGER_ID
DEPENDENCIES = ["preferences"]
MULTI_CONF = True
@ -11,6 +11,8 @@ ratgdo_ns = cg.esphome_ns.namespace("ratgdo")
RATGDO = ratgdo_ns.class_("RATGDOComponent", cg.Component)
SyncFailed = ratgdo_ns.class_("SyncFailed", automation.Trigger.template())
CONF_OUTPUT_GDO = "output_gdo_pin"
DEFAULT_OUTPUT_GDO = (
"D4" # D4 red control terminal / GarageDoorOpener (UART1 TX) pin is D4 on D1 Mini
@ -27,6 +29,9 @@ DEFAULT_REMOTE_ID = 0x539
CONF_RATGDO_ID = "ratgdo_id"
CONF_ON_SYNC_FAILED = "on_sync_failed"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(RATGDO),
@ -42,6 +47,11 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(
CONF_REMOTE_ID, default=DEFAULT_REMOTE_ID
): cv.uint64_t,
cv.Optional(CONF_ON_SYNC_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SyncFailed),
}
),
}
).extend(cv.COMPONENT_SCHEMA)
@ -68,6 +78,10 @@ async def to_code(config):
cg.add(var.set_input_obst_pin(pin))
cg.add(var.set_remote_id(config[CONF_REMOTE_ID]))
for conf in config.get(CONF_ON_SYNC_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
cg.add_library(
name="secplus",
repository="https://github.com/esphome-ratgdo/secplus",

View File

@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "ratgdo.h"
namespace esphome {
namespace ratgdo {
class SyncFailed : public Trigger<> {
public:
explicit SyncFailed(RATGDOComponent* parent)
{
parent->subscribe_sync_failed([this](bool state) {
if (state)
this->trigger();
});
}
};
}
}

View File

@ -16,6 +16,10 @@ namespace ratgdo {
void RATGDOCover::setup()
{
auto state = this->restore_state_();
if (state.has_value()) {
this->parent_->set_door_position(state.value().position);
}
this->parent_->subscribe_door_state([=](DoorState state, float position) {
this->on_door_state(state, position);
});

View File

@ -11,7 +11,7 @@ namespace ratgdo {
void RATGDOLightOutput::dump_config()
{
ESP_LOGCONFIG("", "RATGDO Light");
ESP_LOGCONFIG(TAG, "RATGDO Light");
}
void RATGDOLightOutput::setup()

View File

@ -21,17 +21,25 @@ namespace ratgdo {
void RATGDONumber::setup()
{
float value;
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
if (!this->pref_.load(&value)) {
value = 0;
}
this->publish_state(value);
this->control(value);
if (this->number_type_ == RATGDO_ROLLING_CODE_COUNTER) {
this->parent_->subscribe_rolling_code_counter([=](uint32_t value) {
this->publish_state(value);
this->update_state(value);
});
} else if (this->number_type_ == RATGDO_OPENING_DURATION) {
this->parent_->subscribe_opening_duration([=](float value) {
this->publish_state(value);
this->update_state(value);
});
} else if (this->number_type_ == RATGDO_CLOSING_DURATION) {
this->parent_->subscribe_closing_duration([=](float value) {
this->publish_state(value);
this->update_state(value);
});
}
}
@ -49,6 +57,12 @@ namespace ratgdo {
}
}
void RATGDONumber::update_state(float value)
{
this->pref_.save(&value);
this->publish_state(value);
}
void RATGDONumber::control(float value)
{
if (this->number_type_ == RATGDO_ROLLING_CODE_COUNTER) {
@ -58,6 +72,7 @@ namespace ratgdo {
} else if (this->number_type_ == RATGDO_CLOSING_DURATION) {
this->parent_->set_closing_duration(value);
}
this->pref_.save(&value);
}
} // namespace ratgdo

View File

@ -18,12 +18,18 @@ namespace ratgdo {
public:
void dump_config() override;
void setup() override;
void set_number_type(NumberType number_type_);
void set_number_type(NumberType number_type);
// other esphome components that persist state in the flash have HARDWARE priority
// ensure we get initialized before them, so that the state doesn't get invalidated
// by components that might be added in the future
float get_setup_priority() const override { return setup_priority::HARDWARE + 1; }
void update_state(float value);
void control(float value) override;
protected:
NumberType number_type_;
ESPPreferenceObject pref_;
};
} // namespace ratgdo

View File

@ -44,25 +44,6 @@ namespace ratgdo {
void RATGDOComponent::setup()
{
this->rolling_code_counter_pref_ = global_preferences->make_preference<int>(734874333U);
uint32_t rolling_code_counter = 0;
this->rolling_code_counter_pref_.load(&rolling_code_counter);
this->rolling_code_counter = rolling_code_counter;
// observers are subscribed in the setup() of children defer notify until after setup()
defer([=] { this->rolling_code_counter.notify(); });
this->opening_duration_pref_ = global_preferences->make_preference<float>(734874334U);
float opening_duration = 0;
this->opening_duration_pref_.load(&opening_duration);
this->set_opening_duration(opening_duration);
defer([=] { this->opening_duration.notify(); });
this->closing_duration_pref_ = global_preferences->make_preference<float>(734874335U);
float closing_duration = 0;
this->closing_duration_pref_.load(&closing_duration);
this->set_closing_duration(closing_duration);
defer([=] { this->closing_duration.notify(); });
this->output_gdo_pin_->setup();
this->input_gdo_pin_->setup();
this->input_obst_pin_->setup();
@ -212,8 +193,14 @@ namespace ratgdo {
this->button_state = (byte1 & 1) == 1 ? ButtonState::PRESSED : ButtonState::RELEASED;
ESP_LOGD(TAG, "Open: button=%s", ButtonState_to_string(*this->button_state));
} else if (cmd == Command::OPENINGS) {
// nibble==0 if it's our request
// update openings only from our request or if it's not unknown state
if (nibble == 0 || *this->openings != 0) {
this->openings = (byte1 << 8) | byte2;
ESP_LOGD(TAG, "Openings: %d", *this->openings);
} else {
ESP_LOGD(TAG, "Ignoreing openings, not from our request");
}
} else if (cmd == Command::MOTION) {
this->motion_state = MotionState::DETECTED;
if (*this->light_state == LightState::OFF) {
@ -247,7 +234,6 @@ namespace ratgdo {
{
ESP_LOGD(TAG, "Set opening duration: %.1fs", duration);
this->opening_duration = duration;
this->opening_duration_pref_.save(&this->opening_duration);
if (*this->closing_duration == 0 && duration != 0) {
this->set_closing_duration(duration);
@ -258,7 +244,6 @@ namespace ratgdo {
{
ESP_LOGD(TAG, "Set closing duration: %.1fs", duration);
this->closing_duration = duration;
this->closing_duration_pref_.save(&this->closing_duration);
if (*this->opening_duration == 0 && duration != 0) {
this->set_opening_duration(duration);
@ -269,7 +254,6 @@ namespace ratgdo {
{
ESP_LOGV(TAG, "Set rolling code counter to %d", counter);
this->rolling_code_counter = counter;
this->rolling_code_counter_pref_.save(&this->rolling_code_counter);
}
void RATGDOComponent::increment_rolling_code_counter(int delta)
@ -280,7 +264,7 @@ namespace ratgdo {
void RATGDOComponent::print_packet(const WirePacket& packet) const
{
ESP_LOGV(TAG, "Counter: %d Send code: [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]",
*this->rollingCodeCounter,
*this->rolling_code_counter,
packet[0],
packet[1],
packet[2],
@ -413,8 +397,6 @@ namespace ratgdo {
delayMicroseconds(1260); // "LOW" pulse duration before the message start
this->sw_serial_.write(tx_packet, PACKET_LENGTH);
this->save_rolling_code_counter();
}
void RATGDOComponent::sync()
@ -423,15 +405,23 @@ namespace ratgdo {
this->increment_rolling_code_counter(MAX_CODES_WITHOUT_FLASH_WRITE);
set_retry(
300, 10, [=](uint8_t r) {
500, 10, [=](uint8_t r) {
if (*this->door_state != DoorState::UNKNOWN) { // have status
if (*this->openings != 0) { // have openings
return RetryResult::DONE;
} else {
if (r == 0) { // failed to sync probably rolling counter is wrong, notify
ESP_LOGD(TAG, "Triggering sync failed actions.");
this->sync_failed = true;
};
this->transmit(Command::GET_OPENINGS);
return RetryResult::RETRY;
}
} else {
if (r == 0) { // failed to sync probably rolling counter is wrong, notify
ESP_LOGD(TAG, "Triggering sync failed actions.");
this->sync_failed = true;
};
this->transmit(Command::GET_STATUS);
return RetryResult::RETRY;
}
@ -610,14 +600,6 @@ namespace ratgdo {
this->transmit(Command::LOCK, data::LOCK_TOGGLE);
}
void RATGDOComponent::save_rolling_code_counter()
{
this->rolling_code_counter_pref_.save(&this->rolling_code_counter);
// Forcing a sync results in a soft reset if there are too many
// writes to flash in a short period of time. To avoid this,
// we have configured preferences to write every 5s
}
LightState RATGDOComponent::get_light_state() const
{
return *this->light_state;
@ -674,6 +656,10 @@ namespace ratgdo {
{
this->motion_state.subscribe([=](MotionState state) { defer("motion_state", [=] { f(state); }); });
}
void RATGDOComponent::subscribe_sync_failed(std::function<void(bool)>&& f)
{
this->sync_failed.subscribe(std::move(f));
}
} // namespace ratgdo
} // namespace esphome

View File

@ -26,7 +26,6 @@ extern "C" {
#include "ratgdo_state.h"
namespace esphome {
namespace ratgdo {
@ -121,6 +120,8 @@ namespace ratgdo {
observable<ButtonState> button_state { ButtonState::UNKNOWN };
observable<MotionState> motion_state { MotionState::UNKNOWN };
observable<bool> sync_failed { false };
void set_output_gdo_pin(InternalGPIOPin* pin) { this->output_gdo_pin_ = pin; }
void set_input_gdo_pin(InternalGPIOPin* pin) { this->input_gdo_pin_ = pin; }
void set_input_obst_pin(InternalGPIOPin* pin) { this->input_obst_pin_ = pin; }
@ -136,7 +137,6 @@ namespace ratgdo {
void increment_rolling_code_counter(int delta = 1);
void set_rolling_code_counter(uint32_t code);
void save_rolling_code_counter();
// door
void door_command(uint32_t data);
@ -148,6 +148,7 @@ namespace ratgdo {
void position_sync_while_opening(float delta, float update_period = 500);
void position_sync_while_closing(float delta, float update_period = 500);
void cancel_position_sync_callbacks();
void set_door_position(float door_position) { this->door_position = door_position; }
void set_opening_duration(float duration);
void set_closing_duration(float duration);
@ -179,11 +180,9 @@ namespace ratgdo {
void subscribe_motor_state(std::function<void(MotorState)>&& f);
void subscribe_button_state(std::function<void(ButtonState)>&& f);
void subscribe_motion_state(std::function<void(MotionState)>&& f);
void subscribe_sync_failed(std::function<void(bool)>&& f);
protected:
ESPPreferenceObject rolling_code_counter_pref_;
ESPPreferenceObject opening_duration_pref_;
ESPPreferenceObject closing_duration_pref_;
RATGDOStore isr_store_ {};
SoftwareSerial sw_serial_;

View File

@ -190,6 +190,11 @@
</p>
</div>
<h3>Updating from versions older than 2023.07.07</h3>
<p>
When updating from older versions, save the rolling counter value and restore it via the number entity after flashing the new firmware. If you forget to save the code, check the Home Assistant history.
</p>
<h3>Advanced Users</h3>
<ul>
<li>