#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(CommandType::UNKNOWN): return CommandType::UNKNOWN; case static_cast(CommandType::GET_STATUS): return CommandType::GET_STATUS; case static_cast(CommandType::STATUS): return CommandType::STATUS; case static_cast(CommandType::LOCK): return CommandType::LOCK; case static_cast(CommandType::DOOR_ACTION): return CommandType::DOOR_ACTION; case static_cast(CommandType::LIGHT): return CommandType::LIGHT; case static_cast(CommandType::GET_OPENINGS): return CommandType::GET_OPENINGS; case static_cast(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(cmd_e); } inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) { return cmd_i == static_cast(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 read_command(); void handle_command(const Command& cmd); void send_command(Command cmd, IncrementRollingCode increment = IncrementRollingCode::YES); template 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(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 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 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 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