15#include "esphome/core/log.h"
16#include "esphome/core/application.h"
23static const char *
const TAG =
"home_io_control.soft_phy";
31bool wait_for_air_time(uint32_t sync_us, uint8_t raw_bytes, uint32_t start_ms, uint32_t timeout_ms) {
33 while (micros() - sync_us < needed_us) {
36 if (millis() - start_ms > timeout_ms)
53 uint32_t
const start = millis();
57 if (!this->poll_until_activity_(start, timeout_ms, irq)) {
63 bool early_completed =
false;
64 if (!this->resolve_sync_race_(start, timeout_ms, irq, packet, early_completed)) {
67 if (early_completed) {
72 return this->finalize_receive_(packet, irq);
80bool SoftPhyDriverBase::poll_until_activity_(uint32_t start, uint32_t timeout_ms, uint32_t &irq) {
87 if (millis() - start > timeout_ms) {
100bool SoftPhyDriverBase::resolve_sync_race_(uint32_t start, uint32_t timeout_ms, uint32_t &irq,
RadioRxPacket &packet,
101 bool &early_completed) {
102 early_completed =
false;
110 uint32_t
const sync_us = micros();
114 if (this->try_early_completion_(packet, sync_us, irq, start, timeout_ms)) {
115 early_completed =
true;
118 while (millis() - start <= timeout_ms) {
150bool SoftPhyDriverBase::try_early_completion_(
RadioRxPacket &packet, uint32_t sync_us, uint32_t irq_status,
151 uint32_t start_ms, uint32_t timeout_ms) {
157 auto const offset = (uint8_t) base;
172 if (!wait_for_air_time(sync_us, frame_raw_len, start_ms, timeout_ms))
175 auto const read_len =
184 memcpy(packet.data, probe.decoded + probe.frame_start, probe.frame_len);
185 packet.len = probe.frame_len;
189 this->
fill_capture_info(
true, irq_status, offset, read_len, raw, read_len, packet.data, packet.len);
191#ifdef IOHOME_FRAME_LOG
192 ESP_LOGD(
TAG,
"Early RX: frame_len=%u raw_len=%u air_us=%" PRIu32, frame_len, read_len,
193 (uint32_t) (micros() - sync_us));
194 log_frame(
"RX", packet.data, packet.len, this->current_freq_);
205bool SoftPhyDriverBase::finalize_receive_(
RadioRxPacket &packet, uint32_t irq) {
224 uint8_t raw_reported_len = 0;
225 uint8_t rx_offset = 0;
230 uint8_t
const reported_len = std::min(raw_reported_len, (uint8_t)
sizeof(rx_buf));
231 uint8_t raw_probe_len = reported_len;
232 if (reported_len > 0 && reported_len < 32) {
236 raw_probe_len =
sizeof(rx_buf);
240 if (raw_probe_len > 0)
252#ifdef IOHOME_FRAME_LOG
253 ESP_LOGD(
TAG,
"UART probe: valid=1 bit_offset=%u frame_start=%u frame_len=%u decoded_len=%u", probe.
bit_offset,
257#ifdef IOHOME_FRAME_LOG
260 char hex_buf[97] = {0};
261 uint8_t dump_len = std::min(raw_probe_len, (uint8_t) 32);
262 for (uint8_t i = 0; i < dump_len; i++)
263 snprintf(hex_buf + (i * 3), 4,
"%02X ", rx_buf[i]);
264 ESP_LOGW(
TAG,
"UART probe: valid=0 decoded_len=%u raw_probe_len=%u", probe.
decoded_len, raw_probe_len);
265 ESP_LOGW(
TAG,
" raw[0..%u]: %s", dump_len - 1, hex_buf);
275 uint16_t received = (uint16_t) probe.
decoded[cl] | ((uint16_t) probe.
decoded[cl + 1] << 8);
276 ESP_LOGW(
TAG,
" CRC check: candidate_len=%d cmd=0x%02X crc_computed=0x%04X crc_received=0x%04X %s", cl,
277 test_frame.
cmd, computed, received, computed == received ?
"MATCH" :
"MISMATCH");
285 memcpy(packet.
data, rx_buf, copy_len);
286 packet.
len = copy_len;
289 this->
fill_capture_info(blocking_wait, irq_status, rx_offset, reported_len, rx_buf, raw_probe_len, packet.
data,
292#ifdef IOHOME_FRAME_LOG
294 log_frame(
"RX", packet.
data, packet.
len, this->current_freq_);
297 return packet.
len > 0;
342#ifdef IOHOME_FRAME_LOG
351 if ((uint16_t) len + 2 > (uint16_t)
sizeof(frame_with_crc))
354 memcpy(frame_with_crc, data, len);
355 const uint16_t crc =
crc_ccitt(data, len);
356 frame_with_crc[len] = crc & 0xFF;
357 frame_with_crc[len + 1] = (crc >> 8) & 0xFF;
359 const uint8_t encoded_len =
uart_encode_packet(frame_with_crc, len + 2, tx_buf,
sizeof(tx_buf));
360 if (encoded_len == 0)
376 uint32_t
const start = millis();
380 if (millis() - start > 4000) {
381 ESP_LOGE(
TAG,
"TX timeout — DIO/IRQ pin never fired");
386 delayMicroseconds(100);
399 if (millis() - start > 4000) {
400 ESP_LOGE(
TAG,
"TX timeout — no TX_DONE IRQ (last_irq=0x%08" PRIX32
")", tx_irq);
416 delayMicroseconds(this->post_tx_settle_us_);
bool is_dio_fired() const
Set by the ISR when DIO fires.
virtual void set_mode_standby()=0
Switch to standby mode.
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.
virtual void set_mode_rx()=0
Switch to continuous receive mode.
virtual uint32_t read_irq_status_raw()=0
Read the raw IRQ status word from the radio.
virtual void set_frequency_register(uint32_t freq_hz)=0
Set RF frequency via the chip's own frequency register/opcode encoding, and update current_freq_.
virtual void get_rx_buffer_status(uint8_t &reported_len, uint8_t &rx_offset)=0
Read the chip-reported RX length and buffer offset (raw, before any clamping).
bool is_preamble_detected() override
Check if preamble has been detected (while in RX).
virtual void read_rx_buffer(uint8_t offset, uint8_t *data, uint8_t len)=0
Read len bytes from the RX buffer starting at offset.
virtual bool read_rx_packet(RadioRxPacket &packet, bool blocking_wait, uint32_t irq_status)
Read a received packet from the buffer and return the raw bytes reported by the chip.
virtual uint32_t rx_done_bit() const =0
bool is_sync_detected() override
Check if sync word has been detected (while in RX).
virtual uint32_t preamble_detected_bit() const =0
virtual void configure_buffer_base()
Hook run as part of reset_rx_state_, before re-entering RX.
virtual void set_rx_packet_params()=0
Configure RX-specific packet parameters (preamble detector length, fixed probe length).
virtual uint32_t sync_word_valid_bit() const =0
virtual void fill_capture_info(bool blocking_wait, uint32_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)=0
Populate the RadioCaptureInfo from chip-specific telemetry (RSSI opcode, packet-status byte,...
virtual void clear_irq_status(uint32_t irq_mask)=0
Clear IRQ status bits.
virtual void set_tx_packet_params(uint16_t preamble_len, uint8_t payload_len)=0
Configure TX packet parameters for one outgoing UART-encoded frame.
void reset_rx_state_(bool force_standby=true)
Reset RX state machine and buffer. Optionally force standby first.
virtual uint32_t tx_done_bit() const =0
virtual uint8_t read_rssi_raw_byte()=0
Read the single raw RSSI byte (chip-specific opcode); formula is shared, see read_rssi.
int16_t read_rssi() override
Read instantaneous RSSI (in dBm) while in RX mode.
virtual void before_tx_arm()
Hook run immediately before every SetTx.
virtual void write_tx_buffer(const uint8_t *data, uint8_t len)=0
Write the UART-encoded TX payload into the chip's TX buffer.
virtual int16_t early_rx_read_offset() const
Data-buffer offset an in-flight reception is being written to, or a negative value when this chip mus...
virtual uint32_t activity_irq_mask() const
IRQ bits that count as "activity" for the internal poll_until_activity_() helper and check_for_packet...
bool check_for_packet(RadioRxPacket &packet) override
Non-blocking check for a received packet.
bool wait_for_packet(RadioRxPacket &packet, uint32_t timeout_ms) override
Wait (blocking) for a packet with timeout.
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.
virtual void start_tx()=0
Issue the SetTx opcode with the fixed TX timeout — identical 3-byte payload on both chips,...
void change_frequency(uint32_t freq_hz) override
Change the carrier frequency using fast hop (no standby transition needed).
Shared frame logging helpers for IO-Homecontrol.
static constexpr uint8_t SOFT_PHY_RX_PROBE_PACKET_LEN
Fixed raw-RX probe length: chosen from captures of 23-25 byte protocol frames after UART packing and ...
static constexpr uint8_t FRAME_MIN_SIZE
Minimum frame: CTRL0+CTRL1+DST(3)+SRC(3)+CMD(1).
constexpr uint32_t soft_phy_air_time_us(uint32_t raw_bytes)
On-air time in microseconds for raw_bytes bytes at the protocol's line rate.
static constexpr const char * TAG
uint8_t soft_phy_peek_frame_length(const uint8_t *raw, uint8_t raw_len)
Recover a frame's total length from the very first UART cell of a reception.
uint8_t uart_encode_packet(const uint8_t *data, uint8_t len, uint8_t *encoded, uint8_t encoded_max_len)
UART-encode a buffer of bytes (start bit 0, 8 data bits LSB-first, stop bit 1).
UartProbeResult find_uart_probe(const uint8_t *raw, uint8_t raw_len)
Search raw RX buffer for the best CRC-validated IO-Homecontrol frame.
static constexpr uint8_t SOFT_PHY_EARLY_HEADER_RAW_BYTES
Raw bytes read in the first stage of a length-driven receive — enough to hold CTRL0's UART cell (10 b...
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 FRAME_MAX_SIZE
Maximum frame size (9 header + 23 data).
uint8_t soft_phy_raw_bytes_for_frame(uint8_t frame_len)
Raw on-air bytes needed to carry a whole frame: frame_len protocol bytes plus the two trailing CRC by...
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 uint32_t SOFT_PHY_EARLY_MIN_WINDOW_MS
Smallest receive window a length-driven receive will be attempted in, in milliseconds.
static constexpr uint8_t SOFT_PHY_EARLY_READ_MARGIN_BYTES
Air-time margin added before every mid-reception buffer read, in raw bytes.
static constexpr uint32_t SOFT_PHY_EARLY_POLL_US
Poll interval while waiting out a frame's remaining air time, in microseconds.
constexpr uint8_t RADIO_PACKET_BUFFER_SIZE
Scratch buffer size for raw radio packets and recovered frames.
Shared driver flow for radios using the software PHY (SX1262, LR1121).
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
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.