26constexpr uint32_t BLOCKING_WARNING_THRESHOLD_MS =
28constexpr uint8_t SX1276_VERSION_REGISTER = 0x42;
29constexpr uint8_t SX1276_VERSION_REGISTER_READ_MASK = 0x7F;
30constexpr uint8_t SX1276_EXPECTED_VERSION = 0x12;
61 "Exchange failed: device=%s cmd=0x%02X stage=%s tries=%u saw_challenge=%u cap_valid=%u cap_rx_done=%u "
62 "cap_crc_err=%u cap_freq=%u cap_irq=0x%04X cap_pkt=0x%02X cap_reported_len=%u cap_frame_len=%u cap_rssi=%d",
63 device_id, debug.request_cmd, debug.stage, debug.tries, debug.saw_challenge, debug.capture_valid,
64 debug.capture_rx_done, debug.capture_crc_error, debug.capture_freq_hz, debug.capture_irq_status,
65 debug.capture_packet_status, debug.capture_reported_len, debug.capture_frame_len, debug.capture_rssi_dbm);
90 this->warn_if_blocking_over_ = BLOCKING_WARNING_THRESHOLD_MS;
94 ESP_LOGE(
detail::TAG,
"Invalid node_id or system_key configuration");
102 bool use_sx1262 =
false;
112 this->write_byte(SX1276_VERSION_REGISTER & SX1276_VERSION_REGISTER_READ_MASK);
113 uint8_t
const version = this->read_byte();
115 if (version == SX1276_EXPECTED_VERSION) {
116 ESP_LOGI(
detail::TAG,
"Auto-detected SX1276 (version=0x%02X)", version);
119 ESP_LOGI(
detail::TAG,
"SX1276 not detected (version=0x%02X), trying SX1262", version);
126 ESP_LOGE(
detail::TAG,
"SX1262 requires busy_pin and dio1_pin");
139 this->
radio_ =
new (std::nothrow)
143 if (this->
radio_ ==
nullptr) {
144 ESP_LOGE(
detail::TAG,
"Failed to allocate %s radio driver", use_sx1262 ?
"SX1262" :
"SX1276");
149 if (!this->
radio_->init()) {
158 ESP_LOGI(
detail::TAG,
"Radio initialized (%s), Node ID: %s", use_sx1262 ?
"SX1262" :
"SX1276",
170 uint32_t
const cur = this->
radio_->get_current_freq();
183 this->
radio_->change_frequency(next);
196 uint8_t
const len =
serialize(frame, buf,
sizeof(buf));
203 int16_t
const rssi = this->
radio_->read_rssi();
213 if (!this->
radio_->send_packet(buf, len, tx_config)) {
234 dev->status_poll_interval_ms = poll_interval_ms;
242 if (this->
devices_.count(device_id) != 0)
246 ESP_LOGW(
detail::TAG,
"Ignoring invalid device ID %s", device_id.c_str());
257 auto it = this->
devices_.find(device_id);
258 return (it != this->
devices_.end()) ? &it->second :
nullptr;
270 if (this->
radio_->check_for_packet(packet))
287 uint32_t
const now = millis();
289 if (pair.second.next_update != 0 && now > pair.second.next_update) {
290 bool const should_dispatch_one_shot_poll = pair.second.status_poll_interval_ms == 0;
297 pair.second.next_update = 0;
308 ESP_LOGCONFIG(
detail::TAG,
" Radio: %s", this->
radio_type_.empty() ?
"auto-detected" : this->radio_type_.c_str());
310 LOG_PIN(
" RST Pin: ", this->
rst_pin_);
323 for (
const auto &device_id : pair.second) {
324 ESP_LOGCONFIG(
detail::TAG,
" - remote %s -> device %s", pair.first.c_str(), device_id.c_str());
329 if (this->
radio_ !=
nullptr)
330 this->
radio_->dump_debug();
InternalGPIOPin * fem_en_pin_
Front-end module enable.
InternalGPIOPin * fem_pa_pin_
Front-end module PA switch.
std::map< std::string, IoDevice > devices_
InternalGPIOPin * dio4_pin_
SX1276 DIO4 preamble detect (optional).
void reset_exchange_debug_(uint8_t request_cmd)
InternalGPIOPin * dio1_pin_
SX1262 DIO1 interrupt.
void dump_config() override
Dump configuration and radio debug info to the log.
InternalGPIOPin * rst_pin_
void record_exchange_debug_(const char *stage, uint8_t tries, bool saw_challenge)
virtual IoDevice * get_device(const std::string &device_id)
Retrieve a device by ID; returns nullptr if not found.
virtual void queue_request_device_status(const std::string &device_id)
Queue an async status request; returns immediately, executed in loop().
virtual void add_device(const std::string &device_id)
Add a device to the registry by device ID only (legacy/delegating overload).
InternalGPIOPin * vfem_pin_
Front-end module power.
virtual void set_device_status_poll_interval(const std::string &device_id, uint32_t poll_interval_ms)
Configure the optional follow-up polling interval for a registered device.
void process_pending_operation_()
Pop next pending operation from the queue and execute it (set position, request status,...
void hop_frequency_()
Hop to the next channel in the 3‑channel sequence: CH1 → CH2 → CH3 → CH1.
void loop() override
Main loop: process pending operations and drive radio state machine.
void log_exchange_debug_(const char *device_id) const
std::string radio_type_
"sx1276", "sx1262", or "" (auto-detect)
std::vector< DeviceUpdateCallback > callbacks_
uint8_t system_key_[AES_KEY_SIZE]
ExchangeDebugInfo last_exchange_debug_
bool transmit_frame_(const IoFrame &frame, uint32_t freq, uint16_t preamble)
Transmit a raw IoFrame on the current frequency with given preamble length.
InternalGPIOPin * dio0_pin_
SX1276 DIO0 interrupt.
void notify_device_update_(const std::string &id)
Fire all registered device update callbacks for the given device ID.
std::string system_key_str_
std::map< std::string, std::vector< std::string > > linked_remotes_
Maps remote node IDs to lists of device IDs they control.
void setup() override
Initialize hardware (radio and device registry).
uint8_t node_id_[NODE_ID_SIZE]
uint8_t tcxo_voltage_
SX1262 TCXO voltage setting (default 1.8 V).
void process_received_packet_(const RadioRxPacket &packet)
Parse received packet, update device state if it's a status frame, and notify covers.
InternalGPIOPin * busy_pin_
SX1262 BUSY pin.
SX1262 implementation of RadioDriver.
SX1276 implementation of RadioDriver.
Internal helpers shared by the hub implementation .cpp files.
constexpr const char * TAG
Shared log tag for hub-level messages.
void log_component_capture(const RadioDriver *radio, const char *stage, const uint8_t *buf, uint8_t len, const IoFrame *frame=nullptr)
Log a frame at the "io_capture" tag with structured fields.
void clear_status_poll_tracking(IoDevice &dev)
Clear all bounded follow-up polling state for a device.
bool status_poll_tracking_active(const IoDevice &dev, uint32_t now)
Check whether a device remains inside its bounded follow-up polling window.
void log_frame_issue(IOHomeControlComponent *component, const char *direction, const char *reason, const IoFrame &frame, uint8_t len)
Log a frame‑level issue (unregistered endpoints, unsupported commands).
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
@ UNKNOWN
Unknown/unspecified device.
static constexpr uint8_t LBT_MAX_RETRIES
Max carrier-sense attempts before TX anyway.
static constexpr uint32_t FREQ_CH1
The protocol uses 3 frequency channels in the 868 MHz ISM band.
static constexpr uint32_t FREQ_CH3
Channel 3: 869.85 MHz (2W only).
static constexpr int32_t HOP_TIME_US
Timing constants for frequency hopping and response waiting.
static constexpr uint8_t FRAME_MAX_SIZE
Maximum frame size (9 header + 23 data).
static constexpr uint32_t FREQ_CH2
Channel 2: 868.95 MHz (1W and 2W, TX channel).
static constexpr uint8_t LBT_RETRY_DELAY_MS
Backoff between LBT checks (≥ 5ms per ETSI).
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
bool hex_to_bytes(const std::string &hex, uint8_t *out, uint8_t len)
Convert a hex string (e.g., "123ABC") to a byte array.
static constexpr int16_t LBT_RSSI_THRESHOLD_DBM
Listen-before-talk (LBT) parameters for ETSI EN 300 220 compliance.
uint8_t serialize(const IoFrame &f, uint8_t *buf, uint8_t buf_size)
Serialize a parsed frame into a wire buffer (without CRC).
static const char *const TAG
SX1262 radio driver for IO-Homecontrol.
SX1276 radio driver for IO-Homecontrol.
Debug snapshot of the last exchange attempt.
Runtime state of a paired IO‑Homecontrol device.
bool inverted
True if open/close positions are swapped (e.g., horizontal awning).
uint8_t subtype
Device subtype (manufacturer‑specific).
uint8_t node_id[NODE_ID_SIZE]
Device's 3‑byte radio address.
DeviceType type
Device type (shutter, awning, etc.).
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Diagnostic capture from a radio operation.
uint8_t frame_len
Number of valid bytes in frame[].
uint16_t irq_status
Raw IRQ status register value.
uint8_t packet_status
Packet status byte (chip-specific).
bool crc_error
True if CRC error detected (SX1276: never set in IoHomeOn mode; SX1262: set on bad CRC).
bool valid
True if capture is valid.
uint8_t reported_len
Length reported by the radio chip.
bool rx_done
True if RxDone IRQ fired.
uint32_t freq_hz
RF frequency of capture (Hz).
int16_t rssi_dbm
Received signal strength (dBm).
Raw packet received from the radio.
Configuration for transmitting a packet: carrier frequency and preamble length.
uint16_t preamble_len
Preamble length in symbol periods (bytes).
uint32_t freq_hz
Carrier frequency in Hz.