From bd36a7e6a3e641c1779e9174f992a1b246b04d7d Mon Sep 17 00:00:00 2001
From: Marius Muja
Date: Fri, 7 Jul 2023 15:56:12 -0700
Subject: [PATCH] Make storing persistent state the resposibility of
ratgdo_number (#30)
Co-authored-by: J. Nick Koston
---
README.md | 4 ++
base.yaml | 8 +++
components/ratgdo/__init__.py | 18 +++++-
components/ratgdo/automation.h | 23 +++++++
components/ratgdo/cover/ratgdo_cover.cpp | 4 ++
.../ratgdo/light/ratgdo_light_output.cpp | 2 +-
components/ratgdo/number/ratgdo_number.cpp | 21 ++++++-
components/ratgdo/number/ratgdo_number.h | 8 ++-
components/ratgdo/ratgdo.cpp | 60 +++++++------------
components/ratgdo/ratgdo.h | 11 ++--
static/index.html | 5 ++
11 files changed, 114 insertions(+), 50 deletions(-)
create mode 100644 components/ratgdo/automation.h
diff --git a/README.md b/README.md
index 3ccf7d8..ded19b6 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ The ESPHome firmware will allow you to open the door to any position after calib
+## 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)
diff --git a/base.yaml b/base.yaml
index e1354ad..5042f8a 100644
--- a/base.yaml
+++ b/base.yaml
@@ -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
diff --git a/components/ratgdo/__init__.py b/components/ratgdo/__init__.py
index 4322293..e2f0834 100644
--- a/components/ratgdo/__init__.py
+++ b/components/ratgdo/__init__.py
@@ -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)
@@ -67,6 +77,10 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_INPUT_OBST])
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",
diff --git a/components/ratgdo/automation.h b/components/ratgdo/automation.h
new file mode 100644
index 0000000..2c23603
--- /dev/null
+++ b/components/ratgdo/automation.h
@@ -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();
+ });
+ }
+ };
+
+}
+}
\ No newline at end of file
diff --git a/components/ratgdo/cover/ratgdo_cover.cpp b/components/ratgdo/cover/ratgdo_cover.cpp
index 6fce621..6ff1c8c 100644
--- a/components/ratgdo/cover/ratgdo_cover.cpp
+++ b/components/ratgdo/cover/ratgdo_cover.cpp
@@ -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);
});
diff --git a/components/ratgdo/light/ratgdo_light_output.cpp b/components/ratgdo/light/ratgdo_light_output.cpp
index 9c44b91..976d4aa 100644
--- a/components/ratgdo/light/ratgdo_light_output.cpp
+++ b/components/ratgdo/light/ratgdo_light_output.cpp
@@ -11,7 +11,7 @@ namespace ratgdo {
void RATGDOLightOutput::dump_config()
{
- ESP_LOGCONFIG("", "RATGDO Light");
+ ESP_LOGCONFIG(TAG, "RATGDO Light");
}
void RATGDOLightOutput::setup()
diff --git a/components/ratgdo/number/ratgdo_number.cpp b/components/ratgdo/number/ratgdo_number.cpp
index 276b6c5..27152e0 100644
--- a/components/ratgdo/number/ratgdo_number.cpp
+++ b/components/ratgdo/number/ratgdo_number.cpp
@@ -21,17 +21,25 @@ namespace ratgdo {
void RATGDONumber::setup()
{
+ float value;
+ this->pref_ = global_preferences->make_preference(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
diff --git a/components/ratgdo/number/ratgdo_number.h b/components/ratgdo/number/ratgdo_number.h
index 5b9b55f..08ec6bd 100644
--- a/components/ratgdo/number/ratgdo_number.h
+++ b/components/ratgdo/number/ratgdo_number.h
@@ -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
diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp
index 673971a..6804f69 100644
--- a/components/ratgdo/ratgdo.cpp
+++ b/components/ratgdo/ratgdo.cpp
@@ -44,25 +44,6 @@ namespace ratgdo {
void RATGDOComponent::setup()
{
- this->rolling_code_counter_pref_ = global_preferences->make_preference(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(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(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();
@@ -109,7 +90,7 @@ namespace ratgdo {
uint16_t cmd = ((fixed >> 24) & 0xf00) | (data & 0xff);
data &= ~0xf000; // clear parity nibble
-
+
Command cmd_enum = to_Command(cmd, Command::UNKNOWN);
if ((fixed & 0xfffffff) == this->remote_id_) { // my commands
@@ -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) {
- this->openings = (byte1 << 8) | byte2;
- ESP_LOGD(TAG, "Openings: %d", *this->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&& f)
+ {
+ this->sync_failed.subscribe(std::move(f));
+ }
} // namespace ratgdo
} // namespace esphome
diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h
index 5583292..63425a1 100644
--- a/components/ratgdo/ratgdo.h
+++ b/components/ratgdo/ratgdo.h
@@ -26,7 +26,6 @@ extern "C" {
#include "ratgdo_state.h"
-
namespace esphome {
namespace ratgdo {
@@ -121,6 +120,8 @@ namespace ratgdo {
observable button_state { ButtonState::UNKNOWN };
observable motion_state { MotionState::UNKNOWN };
+ observable 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&& f);
void subscribe_button_state(std::function&& f);
void subscribe_motion_state(std::function&& f);
-
+ void subscribe_sync_failed(std::function&& f);
+
protected:
- ESPPreferenceObject rolling_code_counter_pref_;
- ESPPreferenceObject opening_duration_pref_;
- ESPPreferenceObject closing_duration_pref_;
RATGDOStore isr_store_ {};
SoftwareSerial sw_serial_;
diff --git a/static/index.html b/static/index.html
index fe90709..c094e78 100644
--- a/static/index.html
+++ b/static/index.html
@@ -190,6 +190,11 @@
+ 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.
+
+
Advanced Users