2023-06-05 17:12:51 +00:00
|
|
|
/************************************
|
|
|
|
* Rage
|
|
|
|
* Against
|
|
|
|
* The
|
|
|
|
* Garage
|
|
|
|
* Door
|
|
|
|
* Opener
|
|
|
|
*
|
|
|
|
* Copyright (C) 2022 Paul Wieland
|
|
|
|
*
|
|
|
|
* GNU GENERAL PUBLIC LICENSE
|
|
|
|
************************************/
|
|
|
|
|
|
|
|
#include "ratgdo.h"
|
2023-06-07 14:51:22 +00:00
|
|
|
#include "ratgdo_child.h"
|
2023-06-07 15:07:15 +00:00
|
|
|
#include "ratgdo_state.h"
|
2023-06-07 14:51:22 +00:00
|
|
|
|
2023-06-05 17:12:51 +00:00
|
|
|
#include "esphome/core/log.h"
|
|
|
|
|
2023-06-05 17:13:01 +00:00
|
|
|
namespace esphome {
|
|
|
|
namespace ratgdo {
|
|
|
|
|
2023-06-05 18:56:03 +00:00
|
|
|
static const char* const TAG = "ratgdo";
|
2023-06-24 22:10:41 +00:00
|
|
|
static const int SYNC_DELAY = 2000;
|
2023-06-09 23:49:12 +00:00
|
|
|
static const uint8_t MAX_CODES_WITHOUT_FLASH_WRITE = 3;
|
2023-06-24 23:02:50 +00:00
|
|
|
static const uint32_t FLASH_WRITE_INTERVAL = 10000;
|
2023-06-05 21:26:28 +00:00
|
|
|
|
2023-06-09 23:37:43 +00:00
|
|
|
void IRAM_ATTR HOT RATGDOStore::isrObstruction(RATGDOStore* arg)
|
2023-06-05 21:26:28 +00:00
|
|
|
{
|
2023-06-06 00:59:20 +00:00
|
|
|
if (arg->input_obst.digital_read()) {
|
2023-06-05 21:26:28 +00:00
|
|
|
arg->lastObstructionHigh = millis();
|
|
|
|
} else {
|
2023-06-05 21:30:46 +00:00
|
|
|
arg->obstructionLowCount++;
|
2023-06-05 21:26:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 01:06:12 +00:00
|
|
|
void RATGDOComponent::setup()
|
|
|
|
{
|
2023-06-07 16:11:00 +00:00
|
|
|
this->pref_ = global_preferences->make_preference<int>(734874333U);
|
2023-06-06 01:06:12 +00:00
|
|
|
if (!this->pref_.load(&this->rollingCodeCounter)) {
|
|
|
|
this->rollingCodeCounter = 0;
|
|
|
|
}
|
2023-06-06 00:57:06 +00:00
|
|
|
|
2023-06-06 01:06:12 +00:00
|
|
|
this->output_gdo_pin_->setup();
|
|
|
|
this->input_gdo_pin_->setup();
|
|
|
|
this->input_obst_pin_->setup();
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-06 01:06:12 +00:00
|
|
|
this->store_.input_obst = this->input_obst_pin_->to_isr();
|
2023-06-05 19:39:46 +00:00
|
|
|
|
2023-06-06 01:29:06 +00:00
|
|
|
this->output_gdo_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
|
|
this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
2023-06-06 01:06:12 +00:00
|
|
|
this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT);
|
2023-06-05 23:07:10 +00:00
|
|
|
|
2023-06-17 14:50:38 +00:00
|
|
|
this->swSerial.begin(9600, SWSERIAL_8N1, this->input_gdo_pin_->get_pin(), this->output_gdo_pin_->get_pin(), true);
|
2023-06-06 01:29:06 +00:00
|
|
|
|
2023-06-06 01:06:12 +00:00
|
|
|
this->input_obst_pin_->attach_interrupt(RATGDOStore::isrObstruction, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
2023-06-05 19:39:46 +00:00
|
|
|
|
2023-06-24 23:02:50 +00:00
|
|
|
// save counter to flash every 10s if it changed
|
|
|
|
set_interval(FLASH_WRITE_INTERVAL, std::bind(&RATGDOComponent::saveCounter, this, 1));
|
|
|
|
|
2023-06-10 12:58:51 +00:00
|
|
|
ESP_LOGV(TAG, "Syncing rolling code counter after reboot...");
|
2023-06-24 22:10:41 +00:00
|
|
|
|
|
|
|
// many things happening at startup, use some delay for sync
|
|
|
|
set_timeout(SYNC_DELAY, std::bind(&RATGDOComponent::sync, this));
|
2023-06-06 01:06:12 +00:00
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-06 01:06:12 +00:00
|
|
|
void RATGDOComponent::loop()
|
|
|
|
{
|
|
|
|
obstructionLoop();
|
|
|
|
gdoStateLoop();
|
|
|
|
statusUpdateLoop();
|
|
|
|
}
|
2023-06-05 18:26:26 +00:00
|
|
|
|
2023-06-06 01:37:29 +00:00
|
|
|
void RATGDOComponent::dump_config()
|
|
|
|
{
|
2023-06-06 01:37:27 +00:00
|
|
|
ESP_LOGCONFIG(TAG, "Setting up RATGDO...");
|
2023-06-06 02:36:52 +00:00
|
|
|
LOG_PIN(" Output GDO Pin: ", this->output_gdo_pin_);
|
|
|
|
LOG_PIN(" Input GDO Pin: ", this->input_gdo_pin_);
|
|
|
|
LOG_PIN(" Input Obstruction Pin: ", this->input_obst_pin_);
|
2023-06-07 22:19:48 +00:00
|
|
|
ESP_LOGCONFIG(TAG, " Rolling Code Counter: %d", this->rollingCodeCounter);
|
2023-06-24 21:01:20 +00:00
|
|
|
ESP_LOGCONFIG(TAG, " Remote ID: %d", this->remote_id);
|
2023-06-06 01:37:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 20:38:44 +00:00
|
|
|
const char* cmd_name(uint16_t cmd)
|
|
|
|
{
|
|
|
|
// from: https://github.com/argilo/secplus/blob/f98c3220356c27717a25102c0b35815ebbd26ccc/secplus.py#L540
|
|
|
|
switch (cmd) {
|
|
|
|
// sent by opener (motor)
|
|
|
|
case 0x081:
|
|
|
|
return "status";
|
|
|
|
case 0x084:
|
|
|
|
return "unknown_1";
|
|
|
|
case 0x085:
|
|
|
|
return "unknown_2";
|
|
|
|
case 0x0a1:
|
|
|
|
return "pair_3_resp";
|
|
|
|
case 0x284:
|
|
|
|
return "motor_on";
|
|
|
|
case 0x393:
|
|
|
|
return "learn_3_resp";
|
|
|
|
case 0x401:
|
|
|
|
return "pair_2_resp";
|
|
|
|
case 0x48c:
|
|
|
|
return "openings";
|
|
|
|
|
|
|
|
// sent by switch
|
|
|
|
case 0x080:
|
|
|
|
return "get_status";
|
|
|
|
case 0x0a0:
|
|
|
|
return "pair_3";
|
|
|
|
case 0x181:
|
|
|
|
return "learn_2";
|
|
|
|
case 0x18c:
|
|
|
|
return "lock";
|
|
|
|
case 0x280:
|
|
|
|
return "open";
|
|
|
|
case 0x281:
|
|
|
|
return "light";
|
|
|
|
case 0x285:
|
|
|
|
return "motion";
|
|
|
|
case 0x391:
|
|
|
|
return "learn_1";
|
|
|
|
case 0x392:
|
|
|
|
return "learn_3";
|
|
|
|
case 0x400:
|
|
|
|
return "pair_2";
|
|
|
|
case 0x48b:
|
|
|
|
return "get_openings";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 23:30:56 +00:00
|
|
|
uint16_t RATGDOComponent::readRollingCode()
|
2023-06-06 01:06:12 +00:00
|
|
|
{
|
2023-06-09 23:33:16 +00:00
|
|
|
uint32_t rolling = 0;
|
|
|
|
uint64_t fixed = 0;
|
|
|
|
uint32_t data = 0;
|
|
|
|
|
|
|
|
uint16_t cmd = 0;
|
|
|
|
uint8_t nibble = 0;
|
|
|
|
uint8_t byte1 = 0;
|
|
|
|
uint8_t byte2 = 0;
|
|
|
|
|
|
|
|
decode_wireline(this->rxRollingCode, &rolling, &fixed, &data);
|
|
|
|
|
|
|
|
cmd = ((fixed >> 24) & 0xf00) | (data & 0xff);
|
2023-06-24 20:38:44 +00:00
|
|
|
data &= ~0xf000; // clear parity nibble
|
2023-06-09 23:33:16 +00:00
|
|
|
|
2023-06-24 21:01:20 +00:00
|
|
|
if ((fixed & 0xfff) == this->remote_id) { // my commands
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "[%ld] received mine: rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
ESP_LOGD(TAG, "[%ld] received rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
nibble = (data >> 8) & 0xff;
|
2023-06-09 23:33:16 +00:00
|
|
|
byte1 = (data >> 16) & 0xff;
|
|
|
|
byte2 = (data >> 24) & 0xff;
|
|
|
|
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "cmd=%03x (%s) byte2=%02x byte1=%02x nibble=%01x", cmd, cmd_name(cmd), byte2, byte1, nibble);
|
|
|
|
|
|
|
|
if (cmd == command::STATUS) {
|
|
|
|
auto doorState = static_cast<DoorState>(nibble);
|
|
|
|
if (doorState == DoorState::DOOR_STATE_CLOSED && this->doorState != doorState) {
|
|
|
|
transmit(command::GET_OPENINGS);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->doorState = doorState;
|
|
|
|
this->lightState = static_cast<LightState>((byte2 >> 1) & 1);
|
|
|
|
this->lockState = static_cast<LockState>(byte2 & 1);
|
2023-06-09 23:33:16 +00:00
|
|
|
this->motionState = MotionState::MOTION_STATE_CLEAR; // when the status message is read, reset motion state to 0|clear
|
|
|
|
this->motorState = MotorState::MOTOR_STATE_OFF; // when the status message is read, reset motor state to 0|off
|
2023-06-24 20:38:44 +00:00
|
|
|
// this->obstructionState = static_cast<ObstructionState>((byte1 >> 6) & 1);
|
|
|
|
ESP_LOGD(TAG, "Status: door=%s light=%s lock=%s",
|
|
|
|
door_state_to_string(this->doorState),
|
|
|
|
light_state_to_string(this->lightState),
|
|
|
|
lock_state_to_string(this->lockState));
|
|
|
|
} else if (cmd == command::LIGHT) {
|
|
|
|
if (nibble == 0) {
|
2023-06-10 00:40:09 +00:00
|
|
|
this->lightState = LightState::LIGHT_STATE_OFF;
|
2023-06-24 20:38:44 +00:00
|
|
|
} else if (nibble == 1) {
|
2023-06-10 00:40:09 +00:00
|
|
|
this->lightState = LightState::LIGHT_STATE_ON;
|
2023-06-24 20:38:44 +00:00
|
|
|
} else if (nibble == 2) { // toggle
|
|
|
|
this->lightState = light_state_toggle(this->lightState);
|
2023-06-10 00:40:09 +00:00
|
|
|
}
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "Light: action=%s state=%s",
|
|
|
|
nibble == 0 ? "OFF" : nibble == 1 ? "ON"
|
|
|
|
: "TOGGLE",
|
|
|
|
light_state_to_string(this->lightState));
|
|
|
|
} else if (cmd == command::MOTOR_ON) {
|
2023-06-09 23:33:16 +00:00
|
|
|
this->motorState = MotorState::MOTOR_STATE_ON;
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "Motor: state=%s", motor_state_to_string(this->motorState));
|
|
|
|
} else if (cmd == command::OPEN) {
|
|
|
|
this->buttonState = (byte1 & 1) == 1 ? ButtonState::BUTTON_STATE_PRESSED : ButtonState::BUTTON_STATE_RELEASED;
|
|
|
|
ESP_LOGD(TAG, "Open: button=%s", button_state_to_string(this->buttonState));
|
|
|
|
} else if (cmd == command::OPENINGS) {
|
2023-06-09 23:33:16 +00:00
|
|
|
this->openings = (byte1 << 8) | byte2;
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "Openings: %d", this->openings);
|
|
|
|
} else if (cmd == command::MOTION) {
|
|
|
|
this->motionState = MotionState::MOTION_STATE_DETECTED;
|
|
|
|
if (this->lightState == LightState::LIGHT_STATE_OFF) {
|
|
|
|
transmit(command::GET_STATUS);
|
|
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Motion: %s", motion_state_to_string(this->motionState));
|
2023-06-09 23:33:16 +00:00
|
|
|
} else {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGD(TAG, "Unhandled command: cmd=%03x nibble=%02x byte1=%02x byte2=%02x fixed=%010" PRIx64 " data=%08" PRIx32, cmd, nibble, byte1, byte2, fixed, data);
|
2023-06-06 00:57:06 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
return cmd;
|
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-24 20:38:44 +00:00
|
|
|
void RATGDOComponent::getRollingCode(command::cmd command, uint32_t data, bool increment)
|
2023-06-09 23:33:16 +00:00
|
|
|
{
|
2023-06-24 21:01:20 +00:00
|
|
|
uint64_t fixed = ((command & ~0xff) << 24) | this->remote_id;
|
2023-06-24 20:38:44 +00:00
|
|
|
uint32_t send_data = (data << 8) | (command & 0xff);
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "[%ld] Encode for transmit rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), this->rollingCodeCounter, fixed, send_data);
|
|
|
|
encode_wireline(this->rollingCodeCounter, fixed, send_data, this->txRollingCode);
|
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
printRollingCode();
|
2023-06-24 20:38:44 +00:00
|
|
|
if (increment) {
|
2023-06-09 23:33:16 +00:00
|
|
|
incrementRollingCodeCounter();
|
2023-06-05 18:57:01 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 18:57:01 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::setRollingCodeCounter(uint32_t counter)
|
|
|
|
{
|
2023-06-10 12:58:51 +00:00
|
|
|
ESP_LOGV(TAG, "Set rolling code counter to %d", counter);
|
2023-06-09 23:33:16 +00:00
|
|
|
this->rollingCodeCounter = counter;
|
|
|
|
this->pref_.save(&this->rollingCodeCounter);
|
|
|
|
sendRollingCodeChanged();
|
|
|
|
}
|
2023-06-07 23:10:02 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::incrementRollingCodeCounter()
|
|
|
|
{
|
|
|
|
this->rollingCodeCounter = (this->rollingCodeCounter + 1) & 0xfffffff;
|
|
|
|
sendRollingCodeChanged();
|
|
|
|
}
|
2023-06-07 22:59:08 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::sendRollingCodeChanged()
|
|
|
|
{
|
2023-06-09 23:53:09 +00:00
|
|
|
if (!this->rollingCodeUpdatesEnabled_) {
|
|
|
|
return;
|
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
|
|
|
child->on_rolling_code_change(this->rollingCodeCounter);
|
2023-06-09 23:30:56 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 18:57:01 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::printRollingCode()
|
|
|
|
{
|
2023-06-10 12:58:51 +00:00
|
|
|
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]",
|
2023-06-09 23:33:16 +00:00
|
|
|
this->rollingCodeCounter,
|
|
|
|
this->txRollingCode[0],
|
|
|
|
this->txRollingCode[1],
|
|
|
|
this->txRollingCode[2],
|
|
|
|
this->txRollingCode[3],
|
|
|
|
this->txRollingCode[4],
|
|
|
|
this->txRollingCode[5],
|
|
|
|
this->txRollingCode[6],
|
|
|
|
this->txRollingCode[7],
|
|
|
|
this->txRollingCode[8],
|
|
|
|
this->txRollingCode[9],
|
|
|
|
this->txRollingCode[10],
|
|
|
|
this->txRollingCode[11],
|
|
|
|
this->txRollingCode[12],
|
|
|
|
this->txRollingCode[13],
|
|
|
|
this->txRollingCode[14],
|
|
|
|
this->txRollingCode[15],
|
|
|
|
this->txRollingCode[16],
|
|
|
|
this->txRollingCode[17],
|
|
|
|
this->txRollingCode[18]);
|
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
/*************************** OBSTRUCTION DETECTION ***************************/
|
2023-06-06 01:06:12 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::obstructionLoop()
|
|
|
|
{
|
|
|
|
long currentMillis = millis();
|
|
|
|
static unsigned long lastMillis = 0;
|
|
|
|
|
|
|
|
// the obstruction sensor has 3 states: clear (HIGH with LOW pulse every 7ms), obstructed (HIGH), asleep (LOW)
|
|
|
|
// the transitions between awake and asleep are tricky because the voltage drops slowly when falling asleep
|
|
|
|
// and is high without pulses when waking up
|
|
|
|
|
|
|
|
// If at least 3 low pulses are counted within 50ms, the door is awake, not obstructed and we don't have to check anything else
|
|
|
|
|
|
|
|
// Every 50ms
|
|
|
|
if (currentMillis - lastMillis > 50) {
|
|
|
|
// check to see if we got between 3 and 8 low pulses on the line
|
|
|
|
if (this->store_.obstructionLowCount >= 3 && this->store_.obstructionLowCount <= 8) {
|
|
|
|
// obstructionCleared();
|
|
|
|
this->obstructionState = ObstructionState::OBSTRUCTION_STATE_CLEAR;
|
|
|
|
|
|
|
|
// if there have been no pulses the line is steady high or low
|
|
|
|
} else if (this->store_.obstructionLowCount == 0) {
|
|
|
|
// if the line is high and the last high pulse was more than 70ms ago, then there is an obstruction present
|
|
|
|
if (this->input_obst_pin_->digital_read() && currentMillis - this->store_.lastObstructionHigh > 70) {
|
|
|
|
this->obstructionState = ObstructionState::OBSTRUCTION_STATE_OBSTRUCTED;
|
|
|
|
// obstructionDetected();
|
|
|
|
} else {
|
|
|
|
// asleep
|
|
|
|
}
|
2023-06-09 23:30:56 +00:00
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
lastMillis = currentMillis;
|
|
|
|
this->store_.obstructionLowCount = 0;
|
|
|
|
}
|
|
|
|
}
|
2023-06-09 23:30:56 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::gdoStateLoop()
|
|
|
|
{
|
2023-06-23 23:18:32 +00:00
|
|
|
static bool reading_msg = false;
|
|
|
|
static uint32_t msg_start = 0;
|
|
|
|
static uint16_t byte_count = 0;
|
|
|
|
|
|
|
|
if (!reading_msg) {
|
|
|
|
while (this->swSerial.available()) {
|
|
|
|
uint8_t ser_byte = this->swSerial.read();
|
|
|
|
if (ser_byte != 0x55 && ser_byte != 0x01 && ser_byte != 0x00) {
|
|
|
|
byte_count = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
msg_start = ((msg_start << 8) | ser_byte) & 0xffffff;
|
|
|
|
byte_count++;
|
|
|
|
|
|
|
|
// if we are at the start of a message, capture the next 16 bytes
|
|
|
|
if (msg_start == 0x550100) {
|
|
|
|
this->rxRollingCode[0] = 0x55;
|
|
|
|
this->rxRollingCode[1] = 0x01;
|
|
|
|
this->rxRollingCode[2] = 0x00;
|
|
|
|
|
|
|
|
reading_msg = true;
|
|
|
|
break;
|
|
|
|
}
|
2023-06-09 23:30:56 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-23 23:18:32 +00:00
|
|
|
if (reading_msg) {
|
|
|
|
while (this->swSerial.available()) {
|
|
|
|
uint8_t ser_byte = this->swSerial.read();
|
|
|
|
this->rxRollingCode[byte_count] = ser_byte;
|
|
|
|
byte_count++;
|
|
|
|
|
|
|
|
if (byte_count == CODE_LENGTH) {
|
|
|
|
reading_msg = false;
|
|
|
|
byte_count = 0;
|
2023-06-24 20:38:44 +00:00
|
|
|
if (readRollingCode() == command::STATUS && this->forceUpdate_) {
|
2023-06-23 23:18:32 +00:00
|
|
|
this->forceUpdate_ = false;
|
|
|
|
this->previousDoorState = DoorState::DOOR_STATE_UNKNOWN;
|
|
|
|
this->previousLightState = LightState::LIGHT_STATE_UNKNOWN;
|
|
|
|
this->previousLockState = LockState::LOCK_STATE_UNKNOWN;
|
|
|
|
}
|
|
|
|
return;
|
2023-06-09 19:17:24 +00:00
|
|
|
}
|
2023-06-05 23:19:52 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:19:52 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::statusUpdateLoop()
|
|
|
|
{
|
|
|
|
if (this->doorState != this->previousDoorState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Door state: %s", door_state_to_string(this->doorState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_door_state(this->doorState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousDoorState = this->doorState;
|
|
|
|
}
|
|
|
|
if (this->lightState != this->previousLightState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Light state %s (%d)", light_state_to_string(this->lightState), this->lightState);
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_light_state(this->lightState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousLightState = this->lightState;
|
|
|
|
}
|
|
|
|
if (this->lockState != this->previousLockState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Lock state %s", lock_state_to_string(this->lockState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_lock_state(this->lockState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousLockState = this->lockState;
|
|
|
|
}
|
|
|
|
if (this->obstructionState != this->previousObstructionState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Obstruction state %s", obstruction_state_to_string(this->obstructionState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_obstruction_state(this->obstructionState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousObstructionState = this->obstructionState;
|
|
|
|
}
|
|
|
|
if (this->motorState != this->previousMotorState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Motor state %s", motor_state_to_string(this->motorState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_motor_state(this->motorState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousMotorState = this->motorState;
|
|
|
|
}
|
2023-06-10 00:39:03 +00:00
|
|
|
if (this->motionState != this->previousMotionState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Motion state %s", motion_state_to_string(this->motionState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_motion_state(this->motionState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousMotionState = this->motionState;
|
|
|
|
}
|
|
|
|
if (this->buttonState != this->previousButtonState) {
|
2023-06-24 20:38:44 +00:00
|
|
|
ESP_LOGV(TAG, "Button state %s", button_state_to_string(this->buttonState));
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
2023-06-24 20:38:44 +00:00
|
|
|
child->on_button_state(this->buttonState);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousButtonState = this->buttonState;
|
|
|
|
}
|
|
|
|
if (this->openings != this->previousOpenings) {
|
2023-06-10 12:58:51 +00:00
|
|
|
ESP_LOGV(TAG, "Openings: %d", this->openings);
|
2023-06-09 23:33:16 +00:00
|
|
|
for (auto* child : this->children_) {
|
|
|
|
child->on_openings_change(this->openings);
|
2023-06-09 23:14:13 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
this->previousOpenings = this->openings;
|
2023-06-09 22:36:56 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-07 23:59:49 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::query()
|
|
|
|
{
|
|
|
|
this->forceUpdate_ = true;
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::GET_STATUS);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:19:52 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
/************************* DOOR COMMUNICATION *************************/
|
|
|
|
/*
|
|
|
|
* Transmit a message to the door opener over uart1
|
|
|
|
* The TX1 pin is controlling a transistor, so the logic is inverted
|
|
|
|
* A HIGH state on TX1 will pull the 12v line LOW
|
|
|
|
*
|
|
|
|
* The opener requires a specific duration low/high pulse before it will accept
|
|
|
|
* a message
|
|
|
|
*/
|
2023-06-24 20:38:44 +00:00
|
|
|
void RATGDOComponent::transmit(command::cmd command, uint32_t data, bool increment)
|
2023-06-09 23:33:16 +00:00
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
getRollingCode(command, data, increment);
|
2023-06-09 23:33:16 +00:00
|
|
|
this->output_gdo_pin_->digital_write(true); // pull the line high for 1305 micros so the
|
|
|
|
// door opener responds to the message
|
|
|
|
delayMicroseconds(1305);
|
|
|
|
this->output_gdo_pin_->digital_write(false); // bring the line low
|
|
|
|
|
|
|
|
delayMicroseconds(1260); // "LOW" pulse duration before the message start
|
2023-06-17 14:50:38 +00:00
|
|
|
this->swSerial.write(this->txRollingCode, CODE_LENGTH);
|
2023-06-24 23:02:50 +00:00
|
|
|
|
|
|
|
saveCounter(MAX_CODES_WITHOUT_FLASH_WRITE);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 17:12:51 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::sync()
|
|
|
|
{
|
2023-06-24 22:10:41 +00:00
|
|
|
if (this->rollingCodeCounter == 0) { // first time use
|
|
|
|
this->rollingCodeCounter = 1;
|
|
|
|
// the opener only sends a reply when the rolling code > previous rolling code for a given remote id
|
|
|
|
// when used the first time there is no previous rolling code, so first command is ignored
|
|
|
|
set_timeout(100, [=] {
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::GET_STATUS);
|
2023-06-24 22:10:41 +00:00
|
|
|
});
|
|
|
|
// send it twice since manual says it can take 3 button presses for door to open on first use
|
|
|
|
set_timeout(200, [=] {
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::GET_STATUS);
|
2023-06-24 22:10:41 +00:00
|
|
|
});
|
|
|
|
}
|
2023-06-09 23:49:12 +00:00
|
|
|
for (int i = 0; i <= MAX_CODES_WITHOUT_FLASH_WRITE; i++) {
|
2023-06-24 22:10:41 +00:00
|
|
|
set_timeout(300 + i * 100, [=] {
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::GET_STATUS);
|
2023-06-24 22:10:41 +00:00
|
|
|
});
|
2023-06-09 23:49:12 +00:00
|
|
|
}
|
2023-06-24 22:10:41 +00:00
|
|
|
set_timeout(400 + 100 * MAX_CODES_WITHOUT_FLASH_WRITE, [=] {
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::GET_OPENINGS);
|
2023-06-24 22:10:41 +00:00
|
|
|
});
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:19:52 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::openDoor()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
doorCommand(data::OPEN);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::closeDoor()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
doorCommand(data::CLOSE);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:40:36 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::stopDoor()
|
|
|
|
{
|
|
|
|
if (this->doorState != DoorState::DOOR_STATE_OPENING && this->doorState != DoorState::DOOR_STATE_CLOSING) {
|
2023-06-10 12:58:51 +00:00
|
|
|
ESP_LOGV(TAG, "The door is not moving.");
|
2023-06-09 23:33:16 +00:00
|
|
|
return;
|
2023-06-05 18:56:03 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
toggleDoor();
|
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::toggleDoor()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
doorCommand(data::TOGGLE);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:40:36 +00:00
|
|
|
|
2023-06-24 20:38:44 +00:00
|
|
|
void RATGDOComponent::doorCommand(uint32_t data)
|
2023-06-09 23:33:16 +00:00
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
data |= (1 << 16); // button 1 ?
|
|
|
|
data |= (1 << 8); // button press
|
|
|
|
transmit(command::OPEN, data, false);
|
2023-06-24 22:10:41 +00:00
|
|
|
set_timeout(100, [=] {
|
|
|
|
auto data2 = data & ~(1 << 8); // button release
|
|
|
|
transmit(command::OPEN, data2);
|
|
|
|
});
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-08 01:46:44 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::lightOn()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
this->lightState = LightState::LIGHT_STATE_ON;
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LIGHT, data::ON);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 23:36:08 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::lightOff()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
this->lightState = LightState::LIGHT_STATE_OFF;
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LIGHT, data::OFF);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 18:56:03 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::toggleLight()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
this->lightState = light_state_toggle(this->lightState);
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LIGHT, data::TOGGLE);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-06 00:21:59 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
// Lock functions
|
|
|
|
void RATGDOComponent::lock()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
this->lockState = LockState::LOCK_STATE_LOCKED;
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LOCK, data::ON);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-05 18:26:26 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::unlock()
|
|
|
|
{
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LOCK, data::OFF);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-06 01:06:12 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::toggleLock()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
this->lockState = lock_state_toggle(this->lockState);
|
2023-06-24 23:02:50 +00:00
|
|
|
transmit(command::LOCK, data::TOGGLE);
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-06 01:06:12 +00:00
|
|
|
|
2023-06-24 23:02:50 +00:00
|
|
|
void RATGDOComponent::saveCounter(int threshold)
|
2023-06-09 23:33:16 +00:00
|
|
|
{
|
|
|
|
this->pref_.save(&this->rollingCodeCounter);
|
2023-06-24 23:02:50 +00:00
|
|
|
if (!this->lastSyncedRollingCodeCounter || this->rollingCodeCounter - this->lastSyncedRollingCodeCounter >= threshold) {
|
|
|
|
// do flash write outside of the component loop
|
|
|
|
defer([=] {
|
|
|
|
this->lastSyncedRollingCodeCounter = this->rollingCodeCounter;
|
|
|
|
global_preferences->sync();
|
|
|
|
});
|
2023-06-09 23:43:43 +00:00
|
|
|
}
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-06 01:06:12 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
void RATGDOComponent::register_child(RATGDOClient* obj)
|
|
|
|
{
|
|
|
|
this->children_.push_back(obj);
|
|
|
|
obj->set_parent(this);
|
|
|
|
}
|
|
|
|
LightState RATGDOComponent::getLightState()
|
|
|
|
{
|
2023-06-24 20:38:44 +00:00
|
|
|
return this->lightState;
|
2023-06-09 23:33:16 +00:00
|
|
|
}
|
2023-06-07 14:45:49 +00:00
|
|
|
|
2023-06-09 23:33:16 +00:00
|
|
|
} // namespace ratgdo
|
2023-06-05 17:54:46 +00:00
|
|
|
} // namespace esphome
|