|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Shared RX/TX driver flow for the software-PHY radios (SX1262, LR1121). More...
#include <radio_soft_phy_driver_base.h>
Classes | |
| struct | SoftPhyPacketParams |
| The GFSK SetPacketParams fields both software-PHY chips program identically. More... | |
Public Member Functions | |
| SoftPhyDriverBase (InternalGPIOPin *rst_pin, InternalGPIOPin *busy_pin, uint32_t busy_timeout_ms, uint16_t default_response_preamble, uint16_t default_post_tx_settle_us) | |
| 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 | wait_for_packet (RadioRxPacket &packet, uint32_t timeout_ms) override |
| Wait (blocking) for a packet with timeout. | |
| bool | check_for_packet (RadioRxPacket &packet) override |
| Non-blocking check for a received packet. | |
| void | change_frequency (uint32_t freq_hz) override |
| Change the carrier frequency using fast hop (no standby transition needed). | |
| int16_t | read_rssi () override |
| Read instantaneous RSSI (in dBm) while in RX mode. | |
| bool | is_sync_detected () override |
| Check if sync word has been detected (while in RX). | |
| bool | is_preamble_detected () override |
| Check if preamble has been detected (while in RX). | |
| uint16_t | response_preamble () const override |
| Preamble for response/continuation frames — shared storage, see the concrete drivers' constructors/tuning defaults for the chip-specific rationale and value. | |
| bool | is_failed () const override |
| Returns true if the radio failed to initialize or encountered a fatal error. | |
| Public Member Functions inherited from esphome::home_io_control::RadioDriver | |
| RadioDriver (InternalGPIOPin *rst_pin=nullptr) | |
| virtual | ~RadioDriver ()=default |
| virtual bool | init ()=0 |
| Initialize the radio hardware. Returns true on success. | |
| virtual void | apply_tuning (const TuningConfig &tuning) |
| Apply runtime tuning parameters to the driver. | |
| virtual uint16_t | hop_dwell_ms (const TuningConfig &tuning) const =0 |
| Per-channel dwell for a rotating listen that does not name its own dwell. | |
| virtual bool | has_fast_tx_rx_turnaround () const =0 |
| Whether the chip re-enters RX fast enough after a TX to catch an immediate reply through the standard exchange wait. | |
| virtual void | set_mode_rx ()=0 |
| Switch to continuous receive mode. | |
| virtual void | set_mode_standby ()=0 |
| Switch to standby mode. | |
| virtual const char * | chip_name () const =0 |
| Get a human‑readable chip name. | |
| virtual void | dump_debug () |
| Optional chip-specific diagnostics emitted from dump_config. | |
| uint32_t | get_current_freq () const |
| Get the current RF frequency. | |
| const RadioCaptureInfo & | get_last_capture () const |
| Get the most recent radio capture info. | |
| void | clear_last_capture () |
| Reset the diagnostic capture buffer. | |
| virtual bool | reception_in_progress () |
| True while a frame is arriving on the current channel and retuning would destroy it. | |
| bool | is_dio_fired () const |
| Set by the ISR when DIO fires. | |
| void | clear_dio_fired () |
| void | mark_dio_fired_from_isr () |
Protected Member Functions | |
| void | set_response_preamble_ (uint16_t preamble) |
| Set the preamble length used for response/continuation frames within an exchange. | |
| void | set_post_tx_settle_us_ (uint16_t delay_us) |
| Set the delay between TX completion and re-entering RX. | |
| void | wait_busy_ () |
| Wait until busy_pin_ reads low, feeding the watchdog while polling. | |
| 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. | |
| void | reset_rx_state_ (bool force_standby=true) |
| Reset RX state machine and buffer. Optionally force standby first. | |
| void | rearm_rx_after_tx_ () |
| Minimal path back into RX immediately after a transmission — see the definition for why this is deliberately not reset_rx_state_. | |
| virtual uint32_t | read_irq_status_raw ()=0 |
| Read the raw IRQ status word from the radio. | |
| virtual void | clear_irq_status (uint32_t irq_mask)=0 |
| Clear IRQ status bits. | |
| virtual uint32_t | activity_irq_mask () const |
| IRQ bits that count as "activity" for the internal poll_until_activity_() helper and check_for_packet. | |
| 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 must not be read before RX_DONE. | |
| virtual uint32_t | idle_rx_completion_budget_ms () const |
| Blocking budget for the idle-path length-driven receive, in milliseconds (issue #81). | |
| 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 | set_rx_packet_params ()=0 |
| Configure RX-specific packet parameters (preamble detector length, fixed probe length). | |
| 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. | |
| virtual uint8_t | read_rssi_raw_byte ()=0 |
| Read the single raw RSSI byte (chip-specific opcode); formula is shared, see read_rssi. | |
| 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 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). | |
| 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 void | start_tx ()=0 |
| Issue the SetTx opcode with the fixed TX timeout — identical 3-byte payload on both chips, differing only in opcode/transport, so this stays a thin chip-specific wrapper. | |
| 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, and IRQ-word-width narrowing all differ per chip). | |
| virtual void | before_tx_arm () |
| Hook run immediately before every SetTx. | |
| virtual void | configure_buffer_base () |
| Hook run as part of reset_rx_state_, before re-entering RX. | |
| virtual void | invalidate_stale_rx_content_after_tx () |
| Hook run from rearm_rx_after_tx_, after a transmission and before re-entering RX. | |
Chip-specific IRQ bit values | |
Each driver's own IRQ bit constants, exposed as accessors so the shared RX/TX orchestration never needs to name a chip-specific constant directly. | |
| virtual uint32_t | sync_word_valid_bit () const =0 |
| virtual uint32_t | rx_done_bit () const =0 |
| virtual uint32_t | tx_done_bit () const =0 |
| virtual uint32_t | preamble_detected_bit () const =0 |
| Protected Member Functions inherited from esphome::home_io_control::RadioDriver | |
| void | prepare_blocking_receive_ (RadioRxPacket &packet) |
| Common preamble for blocking receive: clear diagnostics and output packet. | |
| void | prepare_nonblocking_receive_ (RadioRxPacket &packet) |
| Common preamble for non‑blocking receive: clear diagnostics, output packet, and DIO latch. | |
| void | note_reception_in_progress_ () |
| Record that a frame is arriving right now. | |
| void | clear_reception_in_progress_ () |
| Drop the holdoff: the reception ended, was delivered, or was torn down deliberately. | |
| void | reset_hardware_ () |
| Shared hardware reset sequence for chips with an active-low RST pin. | |
| 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. | |
Static Protected Member Functions | |
| static void | build_gfsk_packet_params (const SoftPhyPacketParams &p, uint8_t out[GFSK_PACKET_PARAMS_LEN]) |
| Fill the nine-byte GFSK SetPacketParams payload shared by both chips. | |
Protected Attributes | |
| bool | failed_ {false} |
| Set on a BUSY timeout or a chip-identity check failing; see is_failed. | |
| InternalGPIOPin * | busy_pin_ |
| BUSY line, read directly by both concrete drivers' own dump_debug() in addition to wait_busy_, so this stays protected rather than folding entirely into the private wait-loop state below. | |
| Protected Attributes inherited from esphome::home_io_control::RadioDriver | |
| uint32_t | current_freq_ {FREQ_CH2} |
| RadioCaptureInfo | last_capture_ {} |
| InternalGPIOPin * | rst_pin_ {nullptr} |
| bool | rx_hold_armed_ {false} |
| Idle-hop holdoff latch — see reception_in_progress(). | |
| uint32_t | rx_hold_since_us_ {0} |
| micros() timestamp the holdoff was last (re-)armed at. | |
| volatile bool | dio_fired_ {false} |
Static Protected Attributes | |
| static constexpr uint8_t | GFSK_PACKET_PARAMS_LEN = 9 |
| Byte count of the GFSK SetPacketParams payload — identical on both software-PHY chips. | |
Shared RX/TX driver flow for the software-PHY radios (SX1262, LR1121).
Definition at line 111 of file radio_soft_phy_driver_base.h.
|
inline |
| rst_pin | Active-low hardware reset pin, forwarded to RadioDriver. |
| busy_pin | BUSY line polled by wait_busy_ before every SPI transaction — both concrete drivers are opcode-based chips that require this, unlike the register-based SX1276. |
| busy_timeout_ms | How long wait_busy_ waits for BUSY to drop before declaring the chip failed. Chip-specific (SX1262: 10 ms: RC-oscillator timing; LR1121: 3000 ms, matched to RadioLib's post-reset boot-ROM wait) — this class has no opinion on the value, only on where it's stored and how it's used. |
| default_response_preamble | Chip-specific default for response_preamble (each concrete driver passes its own validated constant — this class has no opinion on the value, only on where it's stored). |
| default_post_tx_settle_us | Chip-specific default post-TX settling delay, same rationale. |
Definition at line 125 of file radio_soft_phy_driver_base.h.
|
inlinenodiscardprotectedvirtual |
IRQ bits that count as "activity" for the internal poll_until_activity_() helper and check_for_packet.
Default is "any bit" — safe only for a driver whose SetDioIrqParams-equivalent mask never includes PreambleDetected in the first place, so a preamble-only reading can never reach this check. Neither current driver qualifies: both SX1262 and LR1121 unmask PreambleDetected (each for its own reason) and override this to exclude it — a preamble-only reading means a frame may still be arriving, and treating it as terminal activity would tear down RX mid-reception.
Reimplemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
Definition at line 252 of file radio_soft_phy_driver_base.h.
|
inlineprotectedvirtual |
Hook run immediately before every SetTx.
No-op by default; LR1121 overrides this to apply its high-ACP TX-quality workaround, which Semtech's own reference applies unconditionally before every SetRx/SetTx.
Reimplemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
Definition at line 309 of file radio_soft_phy_driver_base.h.
|
staticprotected |
Fill the nine-byte GFSK SetPacketParams payload shared by both chips.
Owns the field order and the ×8 preamble conversion: the SetPacketParams preamble field is bit-denominated on both chips (Semtech names it preamble_len_in_bits / pbl_len_in_bit), but every caller in this codebase passes a byte count. Address comparison and whitening are always off.
| p | The chip-independent field values (byte-denominated preamble, chip constants). |
| out | Buffer for exactly GFSK_PACKET_PARAMS_LEN bytes. |
Named without the trailing _ the file's other protected helpers carry: it uses no instance state, so clang-tidy's readability-convert-member-functions-to-static wants it static, and .clang-tidy's ClassMethodCase = lower_case then rejects a trailing _ on a static method. Leaving it static and dropping the underscore keeps clang-tidy quiet without a suppression.
Definition at line 266 of file radio_soft_phy_driver_base.cpp.
|
overridevirtual |
Change the carrier frequency using fast hop (no standby transition needed).
Implements esphome::home_io_control::RadioDriver.
Definition at line 560 of file radio_soft_phy_driver_base.cpp.
|
overridevirtual |
Non-blocking check for a received packet.
Called from loop(). Returns true if a packet was read into packet. Contract:
Implements esphome::home_io_control::RadioDriver.
Definition at line 393 of file radio_soft_phy_driver_base.cpp.
|
protectedpure virtual |
Clear IRQ status bits.
| irq_mask | Bitmask of IRQs to clear (each driver narrows to its own IRQ word width). |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
inlineprotectedvirtual |
Hook run as part of reset_rx_state_, before re-entering RX.
No-op by default; SX1262 overrides this to (re-)write its buffer base address, which LR1121 doesn't need.
Reimplemented in esphome::home_io_control::RadioSX1262.
Definition at line 312 of file radio_soft_phy_driver_base.h.
|
inlinenodiscardprotectedvirtual |
Data-buffer offset an in-flight reception is being written to, or a negative value when this chip must not be read before RX_DONE.
Neither chip's RX_DONE marks the end of the frame: with no hardware framing, RX runs in fixed-length mode at SOFT_PHY_RX_PROBE_PACKET_LEN, so RX_DONE arrives a fixed ~10 ms after the sync word no matter how short the frame actually was. That delay lands squarely on the protocol's tightest turnaround — the hub's reply to a device's challenge — so a driver that can read its buffer while reception is still running opts in here and the shared flow finishes on the frame's own air time instead (see try_early_completion_).
Default is -1: wait for RX_DONE exactly as before. SX1262 overrides it with the RX base address it programs in configure_buffer_base(), which is where a single in-flight packet always starts. LR1121 also opts in, at the base of its one shared TX/RX buffer — see RadioLR1121::early_rx_read_offset for why a wrong guess there is safe only in combination with invalidate_stale_rx_content_after_tx.
Reimplemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
Definition at line 269 of file radio_soft_phy_driver_base.h.
|
protectedpure virtual |
Populate the RadioCaptureInfo from chip-specific telemetry (RSSI opcode, packet-status byte, and IRQ-word-width narrowing all differ per chip).
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedpure virtual |
Read the chip-reported RX length and buffer offset (raw, before any clamping).
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
inlinenodiscardprotectedvirtual |
Blocking budget for the idle-path length-driven receive, in milliseconds (issue #81).
Virtual only so tests can widen it. The host clock stubs advance millis()/micros() by one unit per call (tests/include/esphome/core/hal.h), so a frame's few thousand microseconds of air time also burns a few thousand fake milliseconds — any production-sized budget expires inside wait_for_air_time()'s first stage and the whole path becomes untestable. The existing blocking-path early-completion tests dodge this by passing wait_for_packet() a 20000 ms timeout; this path has no caller-supplied timeout to widen, so the seam has to live here.
Definition at line 279 of file radio_soft_phy_driver_base.h.
|
inlineprotectedvirtual |
Hook run from rearm_rx_after_tx_, after a transmission and before re-entering RX.
No-op by default.
SX1262 has a real address split written once at init (configure_buffer_base): TX always builds at its own base, RX always lands at a different one, so nothing a transmission wrote can ever appear where a length-driven receive (early_rx_read_offset) later reads from. LR1121 has no such split — one shared 256-byte buffer for both directions, and WriteBuffer always starts from the buffer base (see write_buffer_'s doc comment) — so after every LR1121 transmission, the offset a length-driven receive will read from holds a real, CRC-valid, UART-encoded copy of the hub's own last-sent frame. That is not noise: if early_rx_read_offset()'s offset guess ever turns out to be wrong, reading that residue back would pass every stage of try_early_completion_() and hand back the hub's own transmission as a phantom received packet — worse than doing nothing, since check_for_packet() would then tear down and re-arm RX (issue #81's force_standby path) over whatever real reception was actually in progress. RadioLR1121 overrides this to overwrite that offset with a few non-frame-shaped bytes after every TX, so a wrong offset guess degrades back to reading genuine garbage — which correctly fails the length-driven receive's stage 1 or stage 3 — instead of a valid-looking phantom frame.
Reimplemented in esphome::home_io_control::RadioLR1121.
Definition at line 331 of file radio_soft_phy_driver_base.h.
|
inlinenodiscardoverridevirtual |
Returns true if the radio failed to initialize or encountered a fatal error.
Shared storage: both concrete drivers only ever set failed_ from within their own SPI/opcode helpers (a BUSY timeout, a device-identity mismatch, ...), so there is nothing chip-specific left in the accessor itself.
Implements esphome::home_io_control::RadioDriver.
Definition at line 161 of file radio_soft_phy_driver_base.h.
|
overridevirtual |
Check if preamble has been detected (while in RX).
Used together with sync detection to gate frequency hopping.
Consults the private preamble_latched_at_timeout_ flag first; see that member's declaration for why.
Implements esphome::home_io_control::RadioDriver.
Definition at line 574 of file radio_soft_phy_driver_base.cpp.
|
overridevirtual |
Check if sync word has been detected (while in RX).
Used to gate frequency hopping — prevents hopping away mid-frame.
Implements esphome::home_io_control::RadioDriver.
Definition at line 572 of file radio_soft_phy_driver_base.cpp.
|
nodiscardprotectedpure virtual |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedpure virtual |
Read the raw IRQ status word from the radio.
Virtual to allow test doubles (both concrete drivers' tests override this).
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
overridevirtual |
Read instantaneous RSSI (in dBm) while in RX mode.
Used for listen-before-talk (LBT) carrier sense before transmitting.
Same formula on both chips (-(int16_t) raw / 2); only the opcode used to read the single raw byte differs, via read_rssi_raw_byte.
Implements esphome::home_io_control::RadioDriver.
Definition at line 570 of file radio_soft_phy_driver_base.cpp.
|
protectedpure virtual |
Read the single raw RSSI byte (chip-specific opcode); formula is shared, see read_rssi.
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedpure virtual |
Read len bytes from the RX buffer starting at offset.
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedvirtual |
Read a received packet from the buffer and return the raw bytes reported by the chip.
Virtual to allow test doubles (both concrete drivers' tests override this).
Definition at line 301 of file radio_soft_phy_driver_base.cpp.
|
protected |
Minimal path back into RX immediately after a transmission — see the definition for why this is deliberately not reset_rx_state_.
Definition at line 241 of file radio_soft_phy_driver_base.cpp.
|
protected |
Reset RX state machine and buffer. Optionally force standby first.
Definition at line 284 of file radio_soft_phy_driver_base.cpp.
|
inlinenodiscardoverridevirtual |
Preamble for response/continuation frames — shared storage, see the concrete drivers' constructors/tuning defaults for the chip-specific rationale and value.
Reimplemented from esphome::home_io_control::RadioDriver.
Definition at line 155 of file radio_soft_phy_driver_base.h.
|
nodiscardprotectedpure virtual |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
overridevirtual |
Send a packet using the specified carrier frequency and preamble settings.
The driver is responsible for appending the protocol CRC on the air (in hardware or software, depending on the chip).
Implements esphome::home_io_control::RadioDriver.
Definition at line 460 of file radio_soft_phy_driver_base.cpp.
|
protectedpure virtual |
Set RF frequency via the chip's own frequency register/opcode encoding, and update current_freq_.
Called from both change_frequency and the shared send_packet().
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
inlineprotected |
Set the delay between TX completion and re-entering RX.
Definition at line 168 of file radio_soft_phy_driver_base.h.
|
inlineprotected |
Set the preamble length used for response/continuation frames within an exchange.
Definition at line 166 of file radio_soft_phy_driver_base.h.
|
protectedpure virtual |
Configure RX-specific packet parameters (preamble detector length, fixed probe length).
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedpure virtual |
Configure TX packet parameters for one outgoing UART-encoded frame.
| preamble_len | Preamble length in symbols, from the caller's RadioTxConfig. |
| payload_len | UART-encoded payload length in bytes. |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protectedpure virtual |
Issue the SetTx opcode with the fixed TX timeout — identical 3-byte payload on both chips, differing only in opcode/transport, so this stays a thin chip-specific wrapper.
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
nodiscardprotectedpure virtual |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
nodiscardprotectedpure virtual |
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protected |
Wait until busy_pin_ reads low, feeding the watchdog while polling.
Shared verbatim between SX1262 and LR1121 — the two chips differ only in how long they're willing to wait (busy_timeout_ms_). A call short-circuits once the driver has latched failed_: without that guard, every remaining configure_radio_() step after the first failure would re-run the full timeout, turning one bad boot into tens of seconds of hang at the LR1121's 3000 ms timeout (harmless but still pointless at the SX1262's 10 ms one).
Definition at line 48 of file radio_soft_phy_driver_base.cpp.
|
overridevirtual |
Wait (blocking) for a packet with timeout.
Returns true if a packet was received. Contract:
Implements esphome::home_io_control::RadioDriver.
Definition at line 68 of file radio_soft_phy_driver_base.cpp.
|
protectedpure virtual |
Write the UART-encoded TX payload into the chip's TX buffer.
Implemented in esphome::home_io_control::RadioLR1121, and esphome::home_io_control::RadioSX1262.
|
protected |
BUSY line, read directly by both concrete drivers' own dump_debug() in addition to wait_busy_, so this stays protected rather than folding entirely into the private wait-loop state below.
Definition at line 182 of file radio_soft_phy_driver_base.h.
|
protected |
Set on a BUSY timeout or a chip-identity check failing; see is_failed.
Definition at line 178 of file radio_soft_phy_driver_base.h.
|
staticconstexprprotected |
Byte count of the GFSK SetPacketParams payload — identical on both software-PHY chips.
Definition at line 209 of file radio_soft_phy_driver_base.h.