198 lines
5.4 KiB
C++
198 lines
5.4 KiB
C++
#pragma once
|
|
|
|
#include "callbacks.h"
|
|
#include "common.h"
|
|
#include "esphome/components/mqtt/mqtt_client.h"
|
|
#include "esphome/core/optional.h"
|
|
#include "esphome/core/preferences.h"
|
|
#include "observable.h"
|
|
#include "ratgdo_state.h"
|
|
#include "ratgdo_uart_esp32.h"
|
|
|
|
namespace esphome {
|
|
|
|
class Scheduler;
|
|
class InternalGPIOPin;
|
|
|
|
} // namespace esphome
|
|
|
|
namespace esphome::ratgdo {
|
|
class RATGDOComponent;
|
|
|
|
namespace secplus2 {
|
|
|
|
static const uint8_t PACKET_LENGTH = 19;
|
|
typedef uint8_t WirePacket[PACKET_LENGTH];
|
|
|
|
enum class CommandType : uint16_t {
|
|
UNKNOWN = 0x000,
|
|
GET_STATUS = 0x080,
|
|
STATUS = 0x081,
|
|
LOCK = 0x18c,
|
|
DOOR_ACTION = 0x280,
|
|
LIGHT = 0x281,
|
|
GET_OPENINGS = 0x48b,
|
|
OPENINGS = 0x48c // openings = (byte1<<8)+byte2
|
|
};
|
|
|
|
inline const char* CommandType_to_string(CommandType e) {
|
|
switch (e) {
|
|
case CommandType::UNKNOWN:
|
|
return "UNKNOWN";
|
|
case CommandType::GET_STATUS:
|
|
return "GET_STATUS";
|
|
case CommandType::STATUS:
|
|
return "STATUS";
|
|
case CommandType::LOCK:
|
|
return "LOCK";
|
|
case CommandType::DOOR_ACTION:
|
|
return "DOOR_ACTION";
|
|
case CommandType::LIGHT:
|
|
return "LIGHT";
|
|
case CommandType::GET_OPENINGS:
|
|
return "GET_OPENINGS";
|
|
case CommandType::OPENINGS:
|
|
return "OPENINGS";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
inline CommandType to_CommandType(uint16_t t, CommandType unknown) {
|
|
switch (t) {
|
|
case static_cast<uint16_t>(CommandType::UNKNOWN):
|
|
return CommandType::UNKNOWN;
|
|
case static_cast<uint16_t>(CommandType::GET_STATUS):
|
|
return CommandType::GET_STATUS;
|
|
case static_cast<uint16_t>(CommandType::STATUS):
|
|
return CommandType::STATUS;
|
|
case static_cast<uint16_t>(CommandType::LOCK):
|
|
return CommandType::LOCK;
|
|
case static_cast<uint16_t>(CommandType::DOOR_ACTION):
|
|
return CommandType::DOOR_ACTION;
|
|
case static_cast<uint16_t>(CommandType::LIGHT):
|
|
return CommandType::LIGHT;
|
|
case static_cast<uint16_t>(CommandType::GET_OPENINGS):
|
|
return CommandType::GET_OPENINGS;
|
|
case static_cast<uint16_t>(CommandType::OPENINGS):
|
|
return CommandType::OPENINGS;
|
|
default:
|
|
return unknown;
|
|
}
|
|
}
|
|
|
|
inline bool operator==(const uint16_t cmd_i, const CommandType& cmd_e) {
|
|
return cmd_i == static_cast<uint16_t>(cmd_e);
|
|
}
|
|
inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) {
|
|
return cmd_i == static_cast<uint16_t>(cmd_e);
|
|
}
|
|
|
|
enum class IncrementRollingCode {
|
|
NO,
|
|
YES,
|
|
};
|
|
|
|
struct Command {
|
|
CommandType type;
|
|
uint8_t nibble;
|
|
uint8_t byte1;
|
|
uint8_t byte2;
|
|
|
|
Command() : type(CommandType::UNKNOWN) {}
|
|
Command(CommandType type_, uint8_t nibble_ = 0, uint8_t byte1_ = 0,
|
|
uint8_t byte2_ = 0)
|
|
: type(type_), nibble(nibble_), byte1(byte1_), byte2(byte2_) {}
|
|
};
|
|
|
|
class Secplus2 {
|
|
public:
|
|
void setup(RATGDOComponent* ratgdo, Scheduler* scheduler,
|
|
InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin);
|
|
void loop();
|
|
void dump_config();
|
|
void on_shutdown();
|
|
|
|
void sync();
|
|
|
|
void light_action(LightAction action);
|
|
void lock_action(LockAction action);
|
|
void door_action(DoorAction action);
|
|
|
|
void query_status();
|
|
void query_openings();
|
|
|
|
protected:
|
|
void increment_rolling_code_counter(int delta = 1);
|
|
void set_rolling_code_counter(uint32_t counter);
|
|
void set_client_id(uint64_t client_id);
|
|
|
|
optional<Command> read_command();
|
|
void handle_command(const Command& cmd);
|
|
|
|
void send_command(Command cmd,
|
|
IncrementRollingCode increment = IncrementRollingCode::YES);
|
|
template <typename F>
|
|
void send_command(Command cmd, IncrementRollingCode increment, F&& on_sent) {
|
|
// Only register the callback if the command will be accepted.
|
|
// If transmit_pending is set the command will be dropped, and
|
|
// a stale callback would fire when the previous pending packet
|
|
// transmits -- executing logic (e.g. the second phase of a
|
|
// door_command) at the wrong time.
|
|
//
|
|
// Register before send_command() because transmit_packet() may
|
|
// succeed immediately and call on_command_sent_.trigger() inline.
|
|
if (this->flags_.transmit_pending) {
|
|
return;
|
|
}
|
|
this->on_command_sent_(std::forward<F>(on_sent));
|
|
this->send_command(cmd, increment);
|
|
}
|
|
void encode_packet(Command cmd, WirePacket& packet);
|
|
bool transmit_packet();
|
|
|
|
void door_command(DoorAction action);
|
|
|
|
void print_packet(const esphome::LogString* prefix,
|
|
const WirePacket& packet) const;
|
|
optional<Command> decode_packet(const WirePacket& packet) const;
|
|
|
|
void sync_helper(uint32_t start, uint32_t delay, uint8_t tries);
|
|
|
|
// 8-byte member first (may require 8-byte alignment on some 32-bit systems)
|
|
uint64_t client_id_{0x539};
|
|
|
|
// Pointers (4-byte aligned)
|
|
InternalGPIOPin* tx_pin_;
|
|
InternalGPIOPin* rx_pin_;
|
|
RATGDOComponent* ratgdo_;
|
|
Scheduler* scheduler_;
|
|
|
|
// 4-byte members
|
|
uint32_t transmit_pending_start_{0};
|
|
uint32_t rx_msg_start_{0};
|
|
uint32_t rx_last_read_{0};
|
|
|
|
// Larger structures
|
|
single_observable<uint32_t> rolling_code_counter_{0};
|
|
ESPPreferenceObject rolling_code_pref_;
|
|
ESPPreferenceObject client_id_pref_;
|
|
std::string mqtt_rolling_code_topic_;
|
|
std::string mqtt_client_id_topic_;
|
|
OnceCallbacks<void()> on_command_sent_;
|
|
RatgdoUART uart_;
|
|
|
|
// 19-byte arrays
|
|
WirePacket tx_packet_;
|
|
WirePacket rx_packet_;
|
|
|
|
// Small members at the end
|
|
uint16_t rx_byte_count_{0};
|
|
struct {
|
|
uint8_t transmit_pending : 1;
|
|
uint8_t rx_reading_msg : 1;
|
|
} flags_{0};
|
|
};
|
|
} // namespace secplus2
|
|
} // namespace esphome::ratgdo
|