28#include "esphome/core/log.h"
29#include "esphome/core/application.h"
34static const char *
const TAG =
"home_io_control.sx1262";
50static uint8_t
get_bit_msb(
const uint8_t *data, uint16_t bit_pos) {
51 return (data[bit_pos / 8] >> (7 - (bit_pos % 8))) & 0x01;
65static uint8_t
decode_uart_probe(
const uint8_t *raw, uint8_t raw_len, uint8_t bit_offset, uint8_t *decoded,
66 uint8_t decoded_max_len) {
71 uint16_t bit_pos = bit_offset;
72 uint16_t
const total_bits = raw_len * 8;
73 uint8_t decoded_len = 0;
75 while (bit_pos + 10 <= total_bits && decoded_len < decoded_max_len) {
80 for (uint8_t index = 0; index < 8; index++)
81 value |=
get_bit_msb(raw, bit_pos + 1 + index) << index;
83 decoded[decoded_len++] = value;
135 if (candidate_len < 15)
161 uint8_t
const decoded_len =
decode_uart_probe(raw, raw_len, bit_offset, decoded,
sizeof(decoded));
162 if (decoded_len == 0)
168 memcpy(best.
decoded, decoded, decoded_len);
171 for (uint8_t start = 0; start < decoded_len; start++) {
172 uint8_t
const max_candidate_len = std::min<uint8_t>(decoded_len - start,
FRAME_MAX_SIZE);
173 for (
int candidate_len = max_candidate_len; candidate_len >=
FRAME_MIN_SIZE; candidate_len--) {
175 if (!
parse(decoded + start, candidate_len, frame))
185 memcpy(best.
decoded, decoded, decoded_len);
197 if (len == 0 || encoded_max_len == 0)
200 memset(encoded, 0, encoded_max_len);
201 uint16_t bit_pos = 0;
202 const uint16_t total_bits = len * 10;
203 if (((total_bits + 7) / 8) > encoded_max_len)
206 auto write_bit = [encoded](uint16_t pos, uint8_t bit) {
208 encoded[pos / 8] |= 1U << (7 - (pos % 8));
211 for (uint8_t byte_index = 0; byte_index < len; byte_index++) {
212 const uint8_t value = data[byte_index];
214 write_bit(bit_pos++, 0);
215 for (uint8_t bit_index = 0; bit_index < 8; bit_index++)
216 write_bit(bit_pos++, (value >> bit_index) & 0x01);
217 write_bit(bit_pos++, 1);
220 return (total_bits + 7) / 8;
226 uint32_t
const start = millis();
227 while (this->busy_pin_->digital_read()) {
228 if (millis() - start > 10) {
229 ESP_LOGE(
TAG,
"BUSY timeout");
230 this->failed_ =
true;
239 this->spi_->spi_enable();
240 this->spi_->spi_transfer(opcode);
241 for (uint8_t i = 0; i < len; i++)
242 this->spi_->spi_transfer(params[i]);
243 this->spi_->spi_disable();
248 this->spi_->spi_enable();
249 this->spi_->spi_transfer(opcode);
250 this->spi_->spi_transfer(0x00);
251 for (uint8_t i = 0; i < len; i++)
252 data[i] = this->spi_->spi_transfer(0x00);
253 this->spi_->spi_disable();
257 uint8_t irq_raw[2] = {0};
259 return (uint16_t) (((uint16_t) irq_raw[0] << 8) | irq_raw[1]);
269bool RadioSX1262::poll_until_activity_(uint32_t start, uint32_t timeout_ms,
bool &saw_dio1, uint16_t &irq) {
278 if (millis() - start > timeout_ms) {
293bool RadioSX1262::resolve_sync_race_(uint32_t start, uint32_t timeout_ms, uint16_t &irq) {
300 while (millis() - start <= timeout_ms) {
320bool RadioSX1262::finalize_receive_(
RadioRxPacket &packet, uint16_t irq) {
331 this->spi_->spi_enable();
333 this->spi_->spi_transfer((addr >> 8) & 0xFF);
334 this->spi_->spi_transfer(addr & 0xFF);
335 for (uint8_t i = 0; i < len; i++)
336 this->spi_->spi_transfer(data[i]);
337 this->spi_->spi_disable();
342 this->spi_->spi_enable();
344 this->spi_->spi_transfer((addr >> 8) & 0xFF);
345 this->spi_->spi_transfer(addr & 0xFF);
346 this->spi_->spi_transfer(0x00);
347 for (uint8_t i = 0; i < len; i++)
348 data[i] = this->spi_->spi_transfer(0x00);
349 this->spi_->spi_disable();
354 this->spi_->spi_enable();
356 this->spi_->spi_transfer(offset);
357 for (uint8_t i = 0; i < len; i++)
358 this->spi_->spi_transfer(data[i]);
359 this->spi_->spi_disable();
364 this->spi_->spi_enable();
366 this->spi_->spi_transfer(offset);
367 this->spi_->spi_transfer(0x00);
368 for (uint8_t i = 0; i < len; i++)
369 data[i] = this->spi_->spi_transfer(0x00);
370 this->spi_->spi_disable();
377 uint8_t params[9] = {
378 (uint8_t) (preamble_len >> 8),
379 (uint8_t) (preamble_len),
400 uint8_t clear_irq[2] = {
401 (uint8_t) ((irq_mask >> 8) & 0xFF),
402 (uint8_t) (irq_mask & 0xFF),
408 uint8_t errors_raw[2] = {0};
410 return (uint16_t) (((uint16_t) errors_raw[0] << 8) | errors_raw[1]);
414 uint8_t clear_errors[2] = {0x00, 0x00};
419 uint8_t buf_base[2] = {0x00, 0x80};
429 const uint8_t *raw, uint8_t raw_len,
const uint8_t *frame, uint8_t frame_len) {
430 uint8_t packet_status[3] = {0};
452 this->dio1_pin_->setup();
453 this->busy_pin_->setup();
456 if (this->fem_en_pin_ !=
nullptr) {
457 this->fem_en_pin_->setup();
458 this->fem_en_pin_->digital_write(
true);
460 if (this->vfem_pin_ !=
nullptr) {
461 this->vfem_pin_->setup();
462 this->vfem_pin_->digital_write(
true);
464 if (this->fem_pa_pin_ !=
nullptr) {
465 this->fem_pa_pin_->setup();
466 this->fem_pa_pin_->digital_write(
true);
479 ESP_LOGI(
TAG,
"SX1262 initialized");
485 this->spi_->spi_enable();
487 this->spi_->spi_transfer(0x00);
488 this->spi_->spi_disable();
490 uint8_t
const chip_mode = (chip_status >> 4) & 0x07;
491 uint8_t
const cmd_status = (chip_status >> 1) & 0x07;
492 const char *mode_str =
"?";
495 mode_str =
"STDBY_RC";
498 mode_str =
"STDBY_XOSC";
518 uint16_t
const irq = ((uint16_t) irq_raw[0] << 8) | irq_raw[1];
521 ESP_LOGCONFIG(
TAG,
" SX1262 Diagnostic:");
522 ESP_LOGCONFIG(
TAG,
" Chip status: 0x%02X (mode=%s, cmd=%u)", chip_status, mode_str, cmd_status);
523 ESP_LOGCONFIG(
TAG,
" BUSY=%d DIO1=%d", this->busy_pin_->digital_read(), this->dio1_pin_->digital_read());
524 ESP_LOGCONFIG(
TAG,
" Sync word: %02X %02X %02X (expect 57 FD 99)", sync[0], sync[1], sync[2]);
525 ESP_LOGCONFIG(
TAG,
" IRQ status: 0x%04X", irq);
526 ESP_LOGCONFIG(
TAG,
" Device errors: 0x%04X", errors);
531 uint8_t
const stdby_rc = 0x00;
535 uint8_t tcxo_params[4] = {this->tcxo_voltage_, 0x00, 0x01, 0x40};
539 uint8_t
const cal = 0x7F;
544 uint8_t
const stdby_xosc = 0x01;
548 uint8_t
const reg_mode = 0x01;
552 uint8_t
const dio2_rf = 0x01;
560 uint8_t
const pkt_type = 0x00;
567 uint8_t cal_img[2] = {0xD7, 0xDB};
571 uint8_t
const rx_gain = 0x96;
579 uint8_t mod_params[8] = {
591 uint8_t sync_word[8] = {0x57, 0xFD, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00};
597 uint8_t crc_init[2] = {0x1D, 0x0F};
599 uint8_t crc_poly[2] = {0x10, 0x21};
603 uint8_t buf_base[2] = {0x00, 0x80};
607 uint8_t pa_config[4] = {0x04, 0x07, 0x00, 0x01};
611 uint8_t tx_clamp = 0;
617 int8_t
const power = std::max((int8_t) -9, std::min((int8_t) 22, (int8_t) this->tx_power_));
618 uint8_t tx_params[2] = {(uint8_t) power, 0x04};
622 uint8_t irq_params[8] = {
638 uint8_t rx_continuous[3] = {0xFF, 0xFF, 0xFF};
645 uint8_t
const stdby = 0x01;
650 uint8_t rx_continuous[3] = {0xFF, 0xFF, 0xFF};
657 auto freq_reg = (uint32_t) ((
double) freq_hz * (1 << 25) / 32e6);
658 uint8_t params[4] = {
659 (uint8_t) (freq_reg >> 24),
660 (uint8_t) (freq_reg >> 16),
661 (uint8_t) (freq_reg >> 8),
677 return -(int16_t) raw / 2;
686#ifdef IOHOME_FRAME_LOG
697 if ((uint16_t) len + 2 > (uint16_t)
sizeof(frame_with_crc))
700 memcpy(frame_with_crc, data, len);
701 const uint16_t crc =
crc_ccitt(data, len);
702 frame_with_crc[len] = crc & 0xFF;
703 frame_with_crc[len + 1] = (crc >> 8) & 0xFF;
705 const uint8_t encoded_len =
uart_encode_packet(frame_with_crc, len + 2, tx_buf,
sizeof(tx_buf));
706 if (encoded_len == 0)
718 uint8_t tx_timeout[3] = {0x03, 0xE8, 0x00};
721 auto read_irq_status = [
this]() {
722 uint8_t irq_raw[2] = {0};
724 return (uint16_t) (((uint16_t) irq_raw[0] << 8) | irq_raw[1]);
729 uint32_t
const start = millis();
733 if (millis() - start > 4000) {
734 ESP_LOGE(
TAG,
"TX timeout — DIO1 never fired");
739 delayMicroseconds(100);
744 tx_irq = read_irq_status();
752 if (millis() - start > 4000) {
753 ESP_LOGE(
TAG,
"TX timeout — no TX_DONE IRQ (last_irq=0x%04X)", tx_irq);
777 uint32_t
const start = millis();
778 bool saw_dio1 =
false;
782 if (!this->poll_until_activity_(start, timeout_ms, saw_dio1, irq)) {
793 if (!this->resolve_sync_race_(start, timeout_ms, irq)) {
798 return this->finalize_receive_(packet, irq);
804 uint8_t rx_status[2] = {0};
809 uint8_t
const reported_len = std::min(rx_status[0], (uint8_t)
sizeof(rx_buf));
810 uint8_t
const rx_offset = rx_status[1];
811 uint8_t raw_probe_len = reported_len;
812 if (reported_len > 0 && reported_len < 32) {
817 raw_probe_len =
sizeof(rx_buf);
821 if (raw_probe_len > 0)
836 memcpy(packet.
data, rx_buf, copy_len);
837 packet.
len = copy_len;
840 this->
fill_capture_info_(blocking_wait, irq_status, rx_offset, reported_len, rx_buf, raw_probe_len, packet.
data,
843#ifdef IOHOME_FRAME_LOG
845 log_frame(
"RX", packet.
data, packet.
len, this->current_freq_);
848 return packet.
len > 0;
860 uint16_t
const irq = ((uint16_t) irq_raw[0] << 8) | irq_raw[1];
void populate_capture_base_(bool blocking_wait, uint32_t freq_hz, int16_t rssi_dbm, const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len)
Populate the common fields of RadioCaptureInfo from raw telemetry.
InternalGPIOPin * rst_pin_
void mark_dio_fired_from_isr()
void reset_hardware_()
Hardware reset sequence common to all SX chips.
bool is_dio_fired() const
Set by the ISR when DIO fires.
RadioCaptureInfo last_capture_
void prepare_nonblocking_receive_(RadioRxPacket &packet)
Common preamble for non‑blocking receive: clear diagnostics, output packet, and DIO latch.
void prepare_blocking_receive_(RadioRxPacket &packet)
Common preamble for blocking receive: clear diagnostics and output packet.
void wait_busy_()
Wait until BUSY pin is low before any SPI transaction.
void set_mode_rx() override
Switch to continuous receive mode.
int16_t read_rssi() override
Read instantaneous RSSI (in dBm) while in RX mode.
void write_opcode_(uint8_t opcode, const uint8_t *params, uint8_t len)
Write an opcode with optional parameter bytes.
void set_mode_standby() override
Switch to standby mode.
virtual bool read_rx_packet(RadioRxPacket &packet, bool blocking_wait, uint16_t irq_status)
Read a received packet from the buffer and return the raw bytes reported by the chip.
RadioSX1262(SpiAccess *spi, InternalGPIOPin *rst_pin, InternalGPIOPin *dio1_pin, InternalGPIOPin *busy_pin, uint8_t tx_power, uint8_t tcxo_voltage, InternalGPIOPin *fem_en_pin=nullptr, InternalGPIOPin *vfem_pin=nullptr, InternalGPIOPin *fem_pa_pin=nullptr)
void change_frequency(uint32_t freq_hz) override
Change the carrier frequency using fast hop (no standby transition needed).
void write_buffer_(uint8_t offset, const uint8_t *data, uint8_t len)
Write into the SX1262 TX/RX buffer at a given offset.
void reset_rx_state_(bool force_standby=true)
Reset RX state machine and buffer.
void fill_capture_info_(bool blocking_wait, uint16_t irq_status, uint8_t rx_offset, uint8_t reported_len, const uint8_t *raw, uint8_t raw_len, const uint8_t *frame, uint8_t frame_len)
Populate the RadioCaptureInfo from SX1262‑specific telemetry.
void set_frequency_register_(uint32_t freq_hz)
Set RF frequency via the frequency register.
void clear_device_errors_()
Clear device error flags.
void write_register_(uint16_t addr, const uint8_t *data, uint8_t len)
Write to a register (SX1262 uses opcodes for register access).
void set_packet_params_(uint16_t preamble_len, uint8_t payload_len, uint8_t packet_type, uint8_t crc_type)
Configure packet parameters (preamble, payload length, CRC).
void dump_debug() override
Dump SX1262‑specific debug info.
void configure_radio_()
Full radio initialization (called from init()).
virtual uint16_t read_irq_status_raw()
Read the raw IRQ status from the radio.
static void gpio_intr(RadioSX1262 *arg)
DIO1 ISR — sets dio_fired flag. Runs in interrupt context.
bool check_for_packet(RadioRxPacket &packet) override
Non-blocking check for a received packet.
static uint8_t uart_encode_packet(const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len)
Software CRC helper kept for transmit framing parity with the current implementation.
void set_rx_packet_params_()
Apply RX‑specific packet parameters (calls set_packet_params_ for RX).
void read_opcode_(uint8_t opcode, uint8_t *data, uint8_t len)
Read response from an opcode.
bool send_packet(const uint8_t *data, uint8_t len, const RadioTxConfig &tx_config) override
Send a packet using the specified carrier frequency and preamble settings.
bool init() override
Initialize the radio hardware. Returns true on success.
void clear_irq_status_(uint16_t irq_mask)
Clear all IRQ status bits.
void read_buffer_(uint8_t offset, uint8_t *data, uint8_t len)
Read from the SX1262 RX buffer.
void read_register_(uint16_t addr, uint8_t *data, uint8_t len)
Read from a register.
uint16_t get_device_errors_()
Read device error flags (and clear them).
bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override
Wait (blocking) for a packet with timeout.
Shared frame logging helpers for IO-Homecontrol.
static constexpr uint8_t SX1262_SET_DIO3_AS_TCXO_CTRL
static constexpr uint8_t SX1262_CALIBRATE
static constexpr uint16_t SX1262_REG_RX_GAIN
static constexpr uint8_t CMD_DISCOVER_REQ
Broadcast discovery request.
static constexpr uint8_t SX1262_SET_BUFFER_BASE_ADDRESS
static constexpr uint8_t CMD_SET_CONFIG1
Configure device to auto-send status updates.
static constexpr uint8_t CMD_KEY_TRANSFER
Send encrypted system key to device.
static constexpr uint8_t FRAME_MIN_SIZE
Minimum frame: CTRL0+CTRL1+DST(3)+SRC(3)+CMD(1).
static constexpr uint8_t SX1262_CLEAR_DEVICE_ERRORS
static constexpr uint8_t SX1262_GFSK_CRC_OFF
static constexpr uint8_t CMD_ERROR_RESP
Error response to any command.
static constexpr uint8_t SX1262_READ_REGISTER
static constexpr uint8_t CMD_GET_NAME_RESP
Device name response.
static constexpr uint8_t SX1262_SET_PACKET_TYPE
static constexpr uint8_t CMD_DISCOVER_CONFIRM_ACK
Device acknowledges confirmation.
static constexpr uint16_t SX1262_IRQ_SYNC_WORD_VALID
static constexpr uint8_t SX1262_GET_DEVICE_ERRORS
static constexpr uint8_t CMD_STATUS_UPDATE
Device-initiated status update (needs auth).
static constexpr uint16_t SX1262_IRQ_CRC_ERR
static constexpr uint8_t SX1262_SET_MODULATION_PARAMS
static constexpr uint8_t SX1262_GET_RX_BUFFER_STATUS
static constexpr uint8_t CMD_GET_NAME
Request device name.
static constexpr uint8_t CTRL0_PROTOCOL_1W
Bit 5: 1=OneWay protocol, 0=TwoWay protocol.
static constexpr uint8_t CMD_DISCOVER_SPE_RESP
Sub-device response.
static constexpr uint8_t SX1262_CLEAR_IRQ_STATUS
static constexpr uint8_t SX1262_SET_RX
uint16_t crc_ccitt(const uint8_t *data, uint8_t len)
CRC-CCITT used by the IO-Homecontrol protocol for frame validation.
static constexpr uint8_t SX1262_SET_TX
static constexpr uint8_t SX1262_GFSK_PACKET_TYPE_KNOWN_LENGTH
static const uint8_t SX1262_RX_PROBE_PACKET_LEN
static constexpr uint8_t FRAME_MAX_SIZE
Maximum frame size (9 header + 23 data).
static constexpr uint8_t CMD_KEY_CONFIRM
Device confirms key was received.
static constexpr uint8_t SX1262_CALIBRATE_IMAGE
static constexpr uint8_t SX1262_SET_DIO2_AS_RF_SWITCH_CTRL
static bool is_plausible_uart_frame(const IoFrame &frame, uint8_t candidate_len)
static constexpr uint8_t CMD_KEY_INIT
Initiate key transfer to device.
static constexpr uint8_t SX1262_WRITE_BUFFER
static constexpr uint8_t SX1262_READ_BUFFER
static constexpr uint16_t SX1262_IRQ_RX_DONE
static constexpr uint8_t SX1262_SET_PA_CONFIG
static constexpr uint8_t SX1262_SET_TX_PARAMS
static constexpr uint16_t SX1262_IRQ_TX_DONE
static constexpr uint8_t SX1262_SET_DIO_IRQ_PARAMS
bool parse(const uint8_t *buf, uint8_t buf_len, IoFrame &f)
Parse a wire buffer into a parsed IoFrame (validates length and CTRL0).
static constexpr uint8_t CMD_DISCOVER_SPE_REQ
Discover sub-devices (e.g., light on garage door).
static constexpr uint8_t CMD_EXECUTE
Set position/open/close/stop — requires authentication.
static constexpr uint8_t CMD_PRIVATE_RESP
Response to 0x00 and 0x03 (contains position data).
static bool is_known_io_command(uint8_t cmd)
Check if a command ID is one of the known IO‑Homecontrol commands.
static constexpr uint32_t FREQ_CH2
Channel 2: 868.95 MHz (1W and 2W, TX channel).
static constexpr uint8_t SX1262_SET_PACKET_PARAMS
static constexpr uint8_t CMD_CHALLENGE_REQ
Device sends 6-byte random challenge.
static constexpr uint8_t CMD_STATUS_UPDATE_RESP
Acknowledge status update.
static constexpr uint8_t SX1262_GET_STATUS
static constexpr uint16_t SX1262_REG_TX_CLAMP_CONFIG
static uint8_t decode_uart_probe(const uint8_t *raw, uint8_t raw_len, uint8_t bit_offset, uint8_t *decoded, uint8_t decoded_max_len)
Decode a raw UART‑encoded bitstream into bytes.
static uint8_t get_bit_msb(const uint8_t *data, uint16_t bit_pos)
Extract a single bit (MSB‑first) from a byte buffer.
static constexpr uint8_t CMD_SET_CONFIG1_RESP
Config response.
static constexpr uint8_t CMD_DISCOVER_CONFIRM
Confirm discovery to device.
static constexpr uint8_t SX1262_SET_RX_TX_FALLBACK_MODE
static constexpr uint8_t CMD_DISCOVER_RESP
Device responds with its ID and type.
static constexpr uint8_t SX1262_GET_IRQ_STATUS
static constexpr uint8_t SX1262_SET_REGULATOR_MODE
static constexpr uint8_t SX1262_GET_PACKET_STATUS
static constexpr uint8_t CMD_PRIVATE
Get device status — no authentication needed.
static UartProbeResult find_uart_probe(const uint8_t *raw, uint8_t raw_len)
Search a raw capture for the most plausible IoFrame using UART decoding.
static constexpr uint8_t CMD_GET_INFO2_RESP
Device type/model response.
static constexpr uint16_t SX1262_REG_SYNC_WORD
static constexpr uint8_t CMD_CHALLENGE_RESP
Controller responds with HMAC proof.
static constexpr uint8_t SX1262_FALLBACK_STDBY_XOSC
static constexpr uint8_t CMD_GET_INFO2
Request device type/model info.
constexpr uint8_t RADIO_PACKET_BUFFER_SIZE
Scratch buffer size for raw radio packets and recovered frames.
static constexpr uint8_t SX1262_SET_RF_FREQUENCY
static const uint8_t SX1262_SYNC_WORD_PARAM_24_BITS
static constexpr uint8_t SX1262_SET_STANDBY
static constexpr uint8_t SX1262_WRITE_REGISTER
static const uint8_t UART_PROBE_MAX_BIT_OFFSET
Maximum bit offset to search for valid UART decode start position.
static const char *const TAG
static constexpr uint8_t SX1262_GET_RSSI_INST
SX1262 radio driver for IO-Homecontrol.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
uint8_t ctrl0
Control byte 0: flags + length.
Raw packet received from the radio.
uint8_t len
Length of packet in bytes.
uint32_t freq_hz
Frequency the packet was received on (Hz).
uint8_t data[RADIO_PACKET_BUFFER_SIZE]
Raw packet data buffer.
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.
Result of the UART probe: best candidate frame within a raw capture.
uint8_t decoded_len
Total number of bytes decoded at that offset.
uint8_t frame_start
Index into decoded buffer where the frame begins.
bool valid
A plausible frame was found.
uint8_t bit_offset
Bit offset where the best decode started.
uint8_t frame_len
Length of the candidate IoFrame (decoded bytes).
uint8_t decoded[RADIO_PACKET_BUFFER_SIZE]
Full decoded UART stream at the chosen offset.