Rework of sec+1.0 command transmission

This commit is contained in:
Marius Muja 2024-01-12 22:11:39 -08:00
parent ca16559ee6
commit 1fdcd8f8b7
2 changed files with 156 additions and 93 deletions

View File

@ -20,8 +20,6 @@ namespace secplus1 {
this->rx_pin_ = rx_pin;
this->sw_serial_.begin(1200, SWSERIAL_8E1, rx_pin->get_pin(), tx_pin->get_pin(), true);
// this->sw_serial_.enableIntTx(false);
// this->sw_serial_.enableAutoBaud(true);
}
@ -31,6 +29,13 @@ namespace secplus1 {
if (cmd) {
this->handle_command(cmd.value());
}
if (
!this->is_0x37_panel_ &&
this->wall_panel_emulation_state_ != WallPanelEmulationState::RUNNING &&
(millis() - this->last_rx_) > 50
) {
this->do_transmit_if_pending();
}
}
void Secplus1::dump_config()
@ -73,10 +78,21 @@ namespace secplus1 {
return;
} else if (this->wall_panel_emulation_state_ == WallPanelEmulationState::RUNNING) {
// ESP_LOG2(TAG, "[Wall panel emulation] Sending byte: [%02X]", secplus1_states[index]);
this->sw_serial_.write(&secplus1_states[index], 1);
index += 1;
if (index == 18) {
index = 15;
if (index < 15 || !this->do_transmit_if_pending()) {
this->transmit_byte(secplus1_states[index], true);
// gdo response simulation for testing
// auto resp = secplus1_states[index] == 0x39 ? 0x00 :
// secplus1_states[index] == 0x3A ? 0x5C :
// secplus1_states[index] == 0x38 ? 0x52 : 0xFF;
// if (resp != 0xFF) {
// this->transmit_byte(resp, true);
// }
index += 1;
if (index == 18) {
index = 15;
}
}
this->scheduler_->set_timeout(this->ratgdo_, "wall_panel_emulation", 250, [=] {
this->wall_panel_emulation(index);
@ -90,10 +106,12 @@ namespace secplus1 {
if (action == LightAction::UNKNOWN) {
return;
}
if (action == LightAction::TOGGLE ||
if (
action == LightAction::TOGGLE ||
(action == LightAction::ON && this->light_state == LightState::OFF) ||
(action == LightAction::OFF && this->light_state == LightState::ON)) {
this->transmit_packet(toggle_light);
(action == LightAction::OFF && this->light_state == LightState::ON)
) {
this->toggle_light();
}
}
@ -103,14 +121,12 @@ namespace secplus1 {
if (action == LockAction::UNKNOWN) {
return;
}
if (action == LockAction::TOGGLE ||
if (
action == LockAction::TOGGLE ||
(action == LockAction::LOCK && this->lock_state == LockState::UNLOCKED) ||
(action == LockAction::UNLOCK && this->lock_state == LockState::LOCKED)) {
if (this->is_0x37_panel_) {
this->request_lock_toggle_ = true;
} else {
this->transmit_packet(toggle_lock);
}
(action == LockAction::UNLOCK && this->lock_state == LockState::LOCKED)
) {
this->toggle_lock();
}
}
@ -123,49 +139,63 @@ namespace secplus1 {
const uint32_t double_toggle_delay = 1000;
if (action == DoorAction::TOGGLE) {
this->transmit_packet(toggle_door);
this->toggle_door();
} else if (action == DoorAction::OPEN) {
if (this->door_state == DoorState::CLOSED || this->door_state == DoorState::CLOSING) {
this->transmit_packet(toggle_door);
this->toggle_door();
} else if (this->door_state == DoorState::STOPPED) {
this->transmit_packet(toggle_door); // this starts closing door
this->toggle_door(); // this starts closing door
// this changes direction of door
this->scheduler_->set_timeout(this->ratgdo_, "", double_toggle_delay, [=] {
this->transmit_packet(toggle_door);
this->toggle_door();
});
}
} else if (action == DoorAction::CLOSE) {
if (this->door_state == DoorState::OPEN) {
this->transmit_packet(toggle_door);
this->toggle_door();
} else if (this->door_state == DoorState::OPENING) {
this->transmit_packet(toggle_door); // this switches to stopped
this->toggle_door(); // this switches to stopped
// another toggle needed to close
this->scheduler_->set_timeout(this->ratgdo_, "", double_toggle_delay, [=] {
this->transmit_packet(toggle_door);
this->toggle_door();
});
} else if (this->door_state == DoorState::STOPPED) {
this->transmit_packet(toggle_door);
this->toggle_door();
}
} else if (action == DoorAction::STOP) {
if (this->door_state == DoorState::OPENING) {
this->transmit_packet(toggle_door);
this->toggle_door();
} else if (this->door_state == DoorState::CLOSING) {
this->transmit_packet(toggle_door); // this switches to opening
this->toggle_door(); // this switches to opening
// another toggle needed to stop
this->scheduler_->set_timeout(this->ratgdo_, "", double_toggle_delay, [=] {
this->transmit_packet(toggle_door);
this->toggle_door();
});
}
}
}
void Secplus1::toggle_light()
{
this->enqueue_transmit(CommandType::TOGGLE_LIGHT_PRESS);
}
void Secplus1::toggle_lock()
{
this->enqueue_transmit(CommandType::TOGGLE_LOCK_PRESS);
}
void Secplus1::toggle_door()
{
this->enqueue_transmit(CommandType::TOGGLE_DOOR_PRESS);
}
Result Secplus1::call(Args args)
{
return {};
}
optional<Command> Secplus1::read_command()
optional<RxCommand> Secplus1::read_command()
{
static bool reading_msg = false;
static uint32_t msg_start = 0;
@ -236,12 +266,13 @@ namespace secplus1 {
}
optional<Command> Secplus1::decode_packet(const RxPacket& packet) const
optional<RxCommand> Secplus1::decode_packet(const RxPacket& packet) const
{
CommandType cmd_type = to_CommandType(packet[0], CommandType::UNKNOWN);
return Command{cmd_type, packet[1]};
return RxCommand{cmd_type, packet[1]};
}
// unknown meaning of observed command-responses:
// 40 00 and 40 80
// 53 01
@ -249,12 +280,12 @@ namespace secplus1 {
// F8 3F
// FE 3F
void Secplus1::handle_command(const Command& cmd)
void Secplus1::handle_command(const RxCommand& cmd)
{
if (cmd.type == CommandType::DOOR_STATUS) {
if (cmd.req == CommandType::DOOR_STATUS) {
DoorState door_state;
auto val = cmd.value & 0x7;
auto val = cmd.resp & 0x7;
// 000 0x0 stopped
// 001 0x1 opening
// 010 0x2 open
@ -275,86 +306,99 @@ namespace secplus1 {
} else{
door_state = DoorState::UNKNOWN;
}
this->door_state = door_state;
this->ratgdo_->received(door_state);
}
else if (cmd.type == CommandType::DOOR_STATUS_0x37) {
else if (cmd.req == CommandType::DOOR_STATUS_0x37) {
this->is_0x37_panel_ = true;
if (this->request_lock_toggle_) {
this->request_lock_toggle_ = false;
this->sw_serial_.enableIntTx(false);
this->sw_serial_.write(toggle_lock[0]);
ESP_LOG2(TAG, "[%d] Sent byte: [%02X]", millis(), toggle_lock[0]);
this->sw_serial_.enableIntTx(true);
this->scheduler_->set_timeout(this->ratgdo_, "", 3500, [=] {
transmit_byte(toggle_lock[1], false);
});
} else {
if (!this->do_transmit_if_pending()) {
// inject door status request
this->sw_serial_.write(0x38);
this->transmit_byte(static_cast<uint8_t>(CommandType::DOOR_STATUS), true);
}
} else if (cmd.type == CommandType::OTHER_STATUS) {
LightState light_state = to_LightState((cmd.value >> 2) & 1, LightState::UNKNOWN);
} else if (cmd.req == CommandType::OTHER_STATUS) {
LightState light_state = to_LightState((cmd.resp >> 2) & 1, LightState::UNKNOWN);
this->light_state = light_state;
this->ratgdo_->received(light_state);
LockState lock_state = to_LockState((~cmd.value >> 3) & 1, LockState::UNKNOWN);
LockState lock_state = to_LockState((~cmd.resp >> 3) & 1, LockState::UNKNOWN);
this->lock_state = lock_state;
this->ratgdo_->received(lock_state);
}
else if (cmd.type == CommandType::OBSTRUCTION) {
ObstructionState obstruction_state = cmd.value == 0 ? ObstructionState::CLEAR : ObstructionState::OBSTRUCTED;
else if (cmd.req == CommandType::OBSTRUCTION) {
ObstructionState obstruction_state = cmd.resp == 0 ? ObstructionState::CLEAR : ObstructionState::OBSTRUCTED;
this->ratgdo_->received(obstruction_state);
}
else if (cmd.type == CommandType::TOGGLE_DOOR_RELEASE) {
if (cmd.value == 0x31) {
else if (cmd.req == CommandType::TOGGLE_DOOR_RELEASE) {
if (cmd.resp == 0x31) {
this->wall_panel_starting_ = true;
}
} else if (cmd.type == CommandType::TOGGLE_LIGHT_PRESS) {
} else if (cmd.req == CommandType::TOGGLE_LIGHT_PRESS) {
// motion was detected, or the light toggle button was pressed
// either way it's ok to trigger motion detection
if (this->light_state == LightState::OFF) {
this->ratgdo_->received(MotionState::DETECTED);
}
} else if (cmd.type == CommandType::TOGGLE_DOOR_PRESS) {
} else if (cmd.req == CommandType::TOGGLE_DOOR_PRESS) {
this->ratgdo_->received(ButtonState::PRESSED);
} else if (cmd.type == CommandType::TOGGLE_DOOR_RELEASE) {
} else if (cmd.req == CommandType::TOGGLE_DOOR_RELEASE) {
this->ratgdo_->received(ButtonState::RELEASED);
}
}
void Secplus1::transmit_packet(const TxPacket& packet, bool twice, uint32_t delay)
{
this->print_tx_packet(packet);
transmit_byte(packet[0]);
this->scheduler_->set_timeout(this->ratgdo_, "", delay, [=] {
transmit_byte(packet[1], twice);
});
bool Secplus1::do_transmit_if_pending()
{
auto cmd = this->pending_tx();
if (cmd) {
this->enqueue_command_pair(cmd.value());
this->transmit_byte(static_cast<uint32_t>(cmd.value()));
}
return cmd;
}
void Secplus1::transmit_byte(uint32_t value, bool twice)
void Secplus1::enqueue_command_pair(CommandType cmd)
{
int32_t tx_delay = static_cast<int32_t>(this->last_rx_ + 100) - millis();
while (tx_delay<0) {
tx_delay += 250;
}
auto now = millis();
if (cmd == CommandType::TOGGLE_DOOR_PRESS) {
this->enqueue_transmit(CommandType::TOGGLE_DOOR_RELEASE, now + 500);
} else if (cmd == CommandType::TOGGLE_LIGHT_PRESS) {
this->enqueue_transmit(CommandType::TOGGLE_LIGHT_RELEASE, now + 500);
} else if (cmd == CommandType::TOGGLE_LOCK_PRESS) {
this->enqueue_transmit(CommandType::TOGGLE_LOCK_RELEASE, now + 3500);
};
}
this->scheduler_->set_timeout(this->ratgdo_, "", tx_delay, [=] {
this->sw_serial_.enableIntTx(false);
this->sw_serial_.write(value);
this->sw_serial_.enableIntTx(true);
ESP_LOG2(TAG, "[%d] Sent byte: [%02X]", millis(), value);
});
if (twice) {
this->scheduler_->set_timeout(this->ratgdo_, "", tx_delay+40, [=] {
this->sw_serial_.enableIntTx(false);
this->sw_serial_.write(value);
this->sw_serial_.enableIntTx(true);
ESP_LOG2(TAG, "[%d] Sent byte: [%02X]", millis(), value);
});
void Secplus1::enqueue_transmit(CommandType cmd, uint32_t time)
{
if (time == 0) {
time = millis();
}
this->pending_tx_.push(TxCommand{cmd, time});
}
optional<CommandType> Secplus1::pending_tx()
{
if (this->pending_tx_.empty()) {
return {};
}
auto cmd = this->pending_tx_.top();
if (cmd.time < millis()) {
this->pending_tx_.pop();
return cmd.request;
}
return {};
}
void Secplus1::transmit_byte(uint32_t value, bool enable_rx)
{
if (!enable_rx) {
this->sw_serial_.enableIntTx(false);
}
this->sw_serial_.write(value);
if (!enable_rx) {
this->sw_serial_.enableIntTx(true);
}
ESP_LOG2(TAG, "[%d] Sent byte: [%02X]", millis(), value);
}
} // namespace secplus1

View File

@ -1,5 +1,7 @@
#pragma once
#include <queue>
#include "SoftwareSerial.h" // Using espsoftwareserial https://github.com/plerup/espsoftwareserial
#include "esphome/core/optional.h"
@ -44,12 +46,22 @@ namespace secplus1 {
(UNKNOWN, 0xFF),
)
struct Command {
CommandType type;
uint8_t value;
struct RxCommand {
CommandType req;
uint8_t resp;
Command(): type(CommandType::UNKNOWN) {}
Command(CommandType type_, uint8_t value_ = 0) : type(type_), value(value_) {}
RxCommand(): req(CommandType::UNKNOWN), resp(0) {}
RxCommand(CommandType req_): req(req_), resp(0) {}
RxCommand(CommandType req_, uint8_t resp_ = 0) : req(req_), resp(resp_) {}
};
struct TxCommand {
CommandType request;
uint32_t time;
};
struct FirstToSend {
bool operator()(const TxCommand l, const TxCommand r) const { return l.time > r.time; }
};
enum class WallPanelEmulationState {
@ -74,27 +86,34 @@ namespace secplus1 {
protected:
void wall_panel_emulation(size_t index = 0);
optional<Command> read_command();
void handle_command(const Command& cmd);
optional<RxCommand> read_command();
void handle_command(const RxCommand& cmd);
void print_rx_packet(const RxPacket& packet) const;
void print_tx_packet(const TxPacket& packet) const;
optional<Command> decode_packet(const RxPacket& packet) const;
optional<RxCommand> decode_packet(const RxPacket& packet) const;
void transmit_packet(const TxPacket& packet, bool twice = true, uint32_t delay = 500);
void transmit_byte(uint32_t value, bool twice = false);
void enqueue_transmit(CommandType cmd, uint32_t time = 0);
optional<CommandType> pending_tx();
bool do_transmit_if_pending();
void enqueue_command_pair(CommandType cmd);
void transmit_byte(uint32_t value, bool enable_rx = false);
void toggle_light();
void toggle_lock();
void toggle_door();
void query_status();
LightState light_state { LightState::UNKNOWN };
LockState lock_state { LockState::UNKNOWN };
DoorState door_state { DoorState::UNKNOWN };
DoorState prev_door_state { DoorState::UNKNOWN };
bool wall_panel_starting_ { false };
uint32_t wall_panel_emulation_start_ { 0 };
WallPanelEmulationState wall_panel_emulation_state_ { WallPanelEmulationState::WAITING };
bool is_0x37_panel_ { false };
bool request_lock_toggle_ { false };
std::priority_queue<TxCommand, std::vector<TxCommand>, FirstToSend> pending_tx_;
// bool transmit_pending_ { false };
// uint32_t transmit_pending_start_ { 0 };