Rework of door position sync (#61)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Marius Muja 2023-10-08 15:36:09 -07:00 committed by GitHub
parent fe593a9a76
commit 575471bda1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 105 deletions

View File

@ -16,6 +16,9 @@
#include "esphome/core/log.h"
#define ESP_LOG1 ESP_LOGV
#define ESP_LOG2 ESP_LOGV
namespace esphome {
namespace ratgdo {
@ -97,20 +100,19 @@ 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
ESP_LOGV(TAG, "[%ld] received mine: rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
ESP_LOG1(TAG, "[%ld] received mine: rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
return static_cast<uint16_t>(Command::UNKNOWN);
} else {
ESP_LOGV(TAG, "[%ld] received rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
ESP_LOG1(TAG, "[%ld] received rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), rolling, fixed, data);
}
Command cmd_enum = to_Command(cmd, Command::UNKNOWN);
uint8_t nibble = (data >> 8) & 0xff;
uint8_t byte1 = (data >> 16) & 0xff;
uint8_t byte2 = (data >> 24) & 0xff;
ESP_LOGV(TAG, "cmd=%03x (%s) byte2=%02x byte1=%02x nibble=%01x", cmd, Command_to_string(cmd_enum), byte2, byte1, nibble);
ESP_LOG1(TAG, "cmd=%03x (%s) byte2=%02x byte1=%02x nibble=%01x", cmd, Command_to_string(cmd_enum), byte2, byte1, nibble);
if (cmd == Command::STATUS) {
@ -144,26 +146,55 @@ namespace ratgdo {
}
}
if (door_state == DoorState::OPEN) {
this->door_position = 1.0;
} else if (door_state == DoorState::CLOSED) {
this->door_position = 0.0;
} else {
if (*this->closing_duration == 0 || *this->opening_duration == 0 || *this->door_position == DOOR_POSITION_UNKNOWN) {
if (door_state == DoorState::OPENING) {
// door started opening
if (prev_door_state == DoorState::CLOSING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 1.0 - this->door_start_position;
}
this->schedule_door_position_sync();
// this would only get called if no status message is received after door stops moving
// request a status message in that case
set_timeout("door_status_update", (*this->opening_duration + 1) * 1000, [=]() {
this->send_command(Command::GET_STATUS);
});
} else if (door_state == DoorState::CLOSING) {
// door started closing
if (prev_door_state == DoorState::OPENING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 0.0 - this->door_start_position;
}
this->schedule_door_position_sync();
// this would only get called if no status message is received after door stops moving
// request a status message in that case
set_timeout("door_status_update", (*this->closing_duration + 1) * 1000, [=]() {
this->send_command(Command::GET_STATUS);
});
} else if (door_state == DoorState::STOPPED) {
this->door_position_update();
if (*this->door_position == DOOR_POSITION_UNKNOWN) {
this->door_position = 0.5; // best guess
}
}
if (door_state == DoorState::OPENING && !this->moving_to_position) {
this->position_sync_while_opening(1.0 - *this->door_position);
this->moving_to_position = true;
}
if (door_state == DoorState::CLOSING && !this->moving_to_position) {
this->position_sync_while_closing(*this->door_position);
this->moving_to_position = true;
}
if (door_state == DoorState::OPEN || door_state == DoorState::CLOSED || door_state == DoorState::STOPPED) {
this->cancel_position_sync_callbacks();
} else if (door_state == DoorState::OPEN) {
this->door_position = 1.0;
this->cancel_position_sync_callbacks();
} else if (door_state == DoorState::CLOSED) {
this->door_position = 0.0;
this->cancel_position_sync_callbacks();
}
@ -205,7 +236,7 @@ namespace ratgdo {
} else if (cmd == Command::MOTOR_ON) {
this->motor_state = MotorState::ON;
ESP_LOGD(TAG, "Motor: state=%s", MotorState_to_string(*this->motor_state));
} else if (cmd == Command::OPEN) {
} else if (cmd == Command::DOOR_ACTION) {
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) {
@ -231,16 +262,39 @@ namespace ratgdo {
return cmd;
}
void RATGDOComponent::schedule_door_position_sync(float update_period)
{
ESP_LOG1(TAG, "Schedule position sync: delta %f, start position: %f, start moving: %d",
this->door_move_delta, this->door_start_position, this->door_start_moving);
auto duration = this->door_move_delta > 0 ? *this->opening_duration : *this->closing_duration;
auto count = int(1000 * duration / update_period);
set_retry("position_sync_while_moving", update_period, count, [=](uint8_t r) {
this->door_position_update();
return RetryResult::RETRY;
});
}
void RATGDOComponent::door_position_update()
{
if (this->door_start_moving == 0 || this->door_start_position == DOOR_POSITION_UNKNOWN || this->door_move_delta == DOOR_DELTA_UNKNOWN) {
return;
}
auto now = millis();
auto duration = this->door_move_delta > 0 ? *this->opening_duration : -*this->closing_duration;
auto position = this->door_start_position + (now - this->door_start_moving) / (1000 * duration);
ESP_LOG2(TAG, "[%d] Position update: %f", now, position);
this->door_position = clamp(position, 0.0f, 1.0f);
}
void RATGDOComponent::encode_packet(Command command, uint32_t data, bool increment, WirePacket& packet)
{
auto cmd = static_cast<uint64_t>(command);
uint64_t fixed = ((cmd & ~0xff) << 24) | this->remote_id_;
uint32_t send_data = (data << 8) | (cmd & 0xff);
ESP_LOGV(TAG, "[%ld] Encode for transmit rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), *this->rolling_code_counter, fixed, send_data);
ESP_LOG2(TAG, "[%ld] Encode for transmit rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, millis(), *this->rolling_code_counter, fixed, send_data);
encode_wireline(*this->rolling_code_counter, fixed, send_data, packet);
this->print_packet(packet);
if (increment) {
this->increment_rolling_code_counter();
}
@ -271,7 +325,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]",
ESP_LOG2(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->rolling_code_counter,
packet[0],
packet[1],
@ -312,7 +366,7 @@ namespace ratgdo {
const long PULSES_LOWER_LIMIT = 3;
if (current_millis - last_millis > CHECK_PERIOD) {
// ESP_LOGD(TAG, "%ld: Obstruction count: %d, expected: %d, since asleep: %ld",
// ESP_LOGD(TAG, "%ld: Obstruction count: %d, expected: %d, since asleep: %ld",
// current_millis, this->isr_store_.obstruction_low_count, PULSES_EXPECTED,
// current_millis - last_asleep
// );
@ -348,6 +402,7 @@ namespace ratgdo {
while (this->sw_serial_.available()) {
uint8_t ser_byte = this->sw_serial_.read();
if (ser_byte != 0x55 && ser_byte != 0x01 && ser_byte != 0x00) {
ESP_LOG2(TAG, "Ignoring byte: %02X, baud: %d", ser_byte, this->sw_serial_.baudRate());
byte_count = 0;
continue;
}
@ -415,6 +470,9 @@ namespace ratgdo {
delayMicroseconds(100);
}
ESP_LOG2(TAG, "Sending packet");
this->print_packet(this->tx_packet_);
// indicate the start of a frame by pulling the 12V line low for at leat 1 byte followed by
// one STOP bit, which indicates to the receiving end that the start of the message follows
// The output pin is controlling a transistor, so the logic is inverted
@ -447,7 +505,7 @@ namespace ratgdo {
500, MAX_ATTEMPTS, [=](uint8_t r) {
auto result = sync_step();
if (result == RetryResult::RETRY) {
if (r == MAX_ATTEMPTS-2 && *this->door_state == DoorState::UNKNOWN) { // made a few attempts and no progress (door state is the first sync request)
if (r == MAX_ATTEMPTS - 2 && *this->door_state == DoorState::UNKNOWN) { // made a few attempts and no progress (door state is the first sync request)
// increment rolling code counter by some amount in case we crashed without writing to flash the latest value
this->increment_rolling_code_counter(MAX_CODES_WITHOUT_FLASH_WRITE);
}
@ -467,7 +525,6 @@ namespace ratgdo {
if (*this->door_state == DoorState::OPENING) {
return; // gets ignored by opener
}
this->cancel_position_sync_callbacks();
this->door_command(data::DOOR_OPEN);
}
@ -477,7 +534,6 @@ namespace ratgdo {
if (*this->door_state == DoorState::CLOSING || *this->door_state == DoorState::OPENING) {
return; // gets ignored by opener
}
this->cancel_position_sync_callbacks();
this->door_command(data::DOOR_CLOSE);
}
@ -496,59 +552,10 @@ namespace ratgdo {
if (*this->door_state == DoorState::OPENING) {
return; // gets ignored by opener
}
this->cancel_position_sync_callbacks();
this->door_command(data::DOOR_TOGGLE);
}
void RATGDOComponent::position_sync_while_opening(float delta, float update_period)
{
if (*this->opening_duration == 0) {
ESP_LOGW(TAG, "I don't know opening duration, ignoring position sync");
return;
}
auto updates = *this->opening_duration * 1000 * delta / update_period;
auto position_update = delta / updates;
auto count = int(updates);
ESP_LOGV(TAG, "[Opening] Position sync %d times: ", count);
// try to keep position in sync while door is moving
set_retry("position_sync_while_moving", update_period, count, [=](uint8_t r) {
ESP_LOGV(TAG, "[Opening] Position sync: %d: ", r);
this->door_position = *this->door_position + position_update;
return RetryResult::RETRY;
});
// this would only get called if no status message is received after door stops moving
// request a status message in that case, will get cancelled if a status message is received before
set_timeout("door_status_update", (*this->opening_duration + 1) * 1000, [=]() {
this->send_command(Command::GET_STATUS);
});
}
void RATGDOComponent::position_sync_while_closing(float delta, float update_period)
{
if (*this->closing_duration == 0) {
ESP_LOGW(TAG, "I don't know closing duration, ignoring position sync");
return;
}
auto updates = *this->closing_duration * 1000 * delta / update_period;
auto position_update = delta / updates;
auto count = int(updates);
ESP_LOGV(TAG, "[Closing] Position sync %d times: ", count);
// try to keep position in sync while door is moving
set_retry("position_sync_while_moving", update_period, count, [=](uint8_t r) {
ESP_LOGV(TAG, "[Closing] Position sync: %d: ", r);
this->door_position = *this->door_position - position_update;
return RetryResult::RETRY;
});
// this would only get called if no status message is received after door stops moving
// request a status message in that case
set_timeout("door_status_update", (*this->closing_duration + 1) * 1000, [=]() {
this->send_command(Command::GET_STATUS);
});
}
void RATGDOComponent::door_move_to_position(float position)
{
if (*this->door_state == DoorState::OPENING || *this->door_state == DoorState::CLOSING) {
@ -562,50 +569,44 @@ namespace ratgdo {
return;
}
auto duration = delta > 0 ? *this->opening_duration : *this->closing_duration;
auto duration = delta > 0 ? *this->opening_duration : -*this->closing_duration;
if (duration == 0) {
ESP_LOGW(TAG, "I don't know duration, ignoring move to position");
return;
}
if (delta > 0) { // open
this->door_command(data::DOOR_OPEN);
this->position_sync_while_opening(delta);
} else { // close
delta = -delta;
this->door_command(data::DOOR_CLOSE);
this->position_sync_while_closing(delta);
}
auto operation_time = duration * 1000 * delta;
auto operation_time = 1000 * duration * delta;
this->door_move_delta = delta;
ESP_LOGD(TAG, "Moving to position %.2f in %.1fs", position, operation_time / 1000.0);
this->moving_to_position = true;
this->door_command(delta > 0 ? data::DOOR_OPEN : data::DOOR_CLOSE);
set_timeout("move_to_position", operation_time, [=] {
this->door_command(data::DOOR_STOP);
this->moving_to_position = false;
this->door_position = position;
});
}
void RATGDOComponent::cancel_position_sync_callbacks()
{
if (this->moving_to_position) {
if (this->door_start_moving != 0) {
ESP_LOGD(TAG, "Cancelling position callbacks");
cancel_timeout("move_to_position");
cancel_retry("position_sync_while_moving");
cancel_timeout("door_status_update");
this->door_start_moving = 0;
this->door_start_position = DOOR_POSITION_UNKNOWN;
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
moving_to_position = false;
}
void RATGDOComponent::door_command(uint32_t data)
{
data |= (1 << 16); // button 1 ?
data |= (1 << 8); // button press
this->send_command(Command::OPEN, data, false);
set_timeout(100, [=] {
this->send_command(Command::DOOR_ACTION, data, false);
set_timeout(200, [=] {
auto data2 = data & ~(1 << 8); // button release
this->send_command(Command::OPEN, data2);
this->send_command(Command::DOOR_ACTION, data2);
});
}

View File

@ -36,6 +36,7 @@ namespace ratgdo {
typedef uint8_t WirePacket[PACKET_LENGTH];
const float DOOR_POSITION_UNKNOWN = -1.0;
const float DOOR_DELTA_UNKNOWN = -2.0;
namespace data {
const uint32_t LIGHT_OFF = 0;
@ -64,7 +65,7 @@ namespace ratgdo {
(LEARN_2, 0x181),
(LOCK, 0x18c),
(OPEN, 0x280),
(DOOR_ACTION, 0x280),
(LIGHT, 0x281),
(MOTOR_ON, 0x284),
(MOTION, 0x285),
@ -88,7 +89,7 @@ namespace ratgdo {
struct RATGDOStore {
int obstruction_low_count = 0; // count obstruction low pulses
static void IRAM_ATTR HOT isr_obstruction(RATGDOStore* arg)
static void IRAM_ATTR HOT isr_obstruction(RATGDOStore* arg)
{
arg->obstruction_low_count++;
}
@ -111,7 +112,10 @@ namespace ratgdo {
observable<DoorState> door_state { DoorState::UNKNOWN };
observable<float> door_position { DOOR_POSITION_UNKNOWN };
bool moving_to_position { false };
unsigned long door_start_moving { 0 };
float door_start_position { DOOR_POSITION_UNKNOWN };
float door_move_delta { DOOR_DELTA_UNKNOWN };
observable<LightState> light_state { LightState::UNKNOWN };
observable<LockState> lock_state { LockState::UNKNOWN };
@ -146,12 +150,12 @@ namespace ratgdo {
void close_door();
void stop_door();
void door_move_to_position(float position);
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);
void schedule_door_position_sync(float update_period = 500);
void door_position_update();
void cancel_position_sync_callbacks();
// light
void toggle_light();