Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
hub_core.h
Go to the documentation of this file.
1#pragma once
2
3/// @file hub_core.h
4/// @brief IO-Homecontrol ESPHome component — protocol controller.
5///
6/// This component manages the IO-Homecontrol 2W protocol: sending commands,
7/// receiving responses with automatic authentication, device discovery/pairing,
8/// and device state tracking. Radio hardware is delegated to a RadioDriver
9/// implementation (SX1276, SX1262, etc.).
10///
11/// SPI configuration: MSB first, CPOL=0, CPHA=0 (Mode 0), 8 MHz clock.
12/// The component inherits SPIDevice and implements SpiAccess to bridge
13/// the ESPHome SPI framework to the radio driver.
14///
15/// Architecture notes:
16/// - setup() initializes radio, waits for YAML-driven device registration, and enters RX mode.
17/// - loop() processes the pending_operations_ queue (serializes all radio work).
18/// - All outbound commands go through send_and_receive_ which handles retry & auth.
19/// - Inbound frames are processed in process_received_packet_ and may trigger
20/// inbound authentication (hub_exchange.h) if the device proves itself.
21/// - Device registry and callbacks provide fan‑out to platform entities (covers/lights/switches).
22
23#include "esphome/core/component.h"
24#include "esphome/core/hal.h"
25#include "esphome/components/spi/spi.h"
26#include "esphome/components/button/button.h"
27#include "proto_frame.h"
28#include "radio_interface.h"
29#include "hub_exchange.h"
30#include "hub_decisions.h"
31#include "hub_pairing.h"
32#include <deque>
33#include <map>
34#include <vector>
35#include <functional>
36
37namespace esphome {
38namespace home_io_control {
39
40/// Callback type for notifying covers of device state changes.
41using DeviceUpdateCallback = std::function<void(const std::string &device_id, const IoDevice &device)>;
42
43inline constexpr uint8_t DEFAULT_TX_POWER_DBM = 17; ///< Default TX power used unless YAML overrides it.
44inline constexpr uint8_t DEFAULT_PA_PIN_PA_BOOST = 0x80; ///< SX1276 PA_CONFIG selector for the PA_BOOST output path.
45inline constexpr uint8_t DEFAULT_TCXO_VOLTAGE_SETTING_1P8V = 0x03; ///< SX1262 DIO3 setting value for a 1.8 V TCXO.
46inline constexpr size_t POSITION_TEXT_BUFFER_SIZE = 16; ///< Buffer for formatted position strings such as "100%".
47
48// ============================================================================
49// Main Component
50// ============================================================================
51
52/// The main IO-Homecontrol component. Manages the protocol layer and delegates
53/// radio operations to a RadioDriver instance.
54///
55/// Inherits SPIDevice so that ESPHome's Python codegen can configure SPI pins.
56/// Implements SpiAccess to provide the radio driver with SPI bus access.
57class IOHomeControlComponent : public Component,
58 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
59 spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_8MHZ>,
60 public SpiAccess {
61 public:
62 /// @brief Initialize hardware (radio and device registry).
63 void setup() override;
64 /// @brief Main loop: process pending operations and drive radio state machine.
65 void loop() override;
66 /// @brief Dump configuration and radio debug info to the log.
67 void dump_config() override;
68 /// @brief Get setup priority (HARDWARE to initialize early).
69 /// @return setup_priority::HARDWARE.
70 [[nodiscard]] float get_setup_priority() const override { return setup_priority::HARDWARE; }
71
72 // --- SpiAccess implementation (delegates to SPIDevice) ---
73 /// @brief Enable the SPI bus.
74 void spi_enable() override { this->enable(); }
75 /// @brief Disable the SPI bus.
76 void spi_disable() override { this->disable(); }
77 /// @brief Transfer one byte full‑duplex.
78 /// @param data Byte to send.
79 /// @return Received byte.
80 uint8_t spi_transfer(uint8_t data) override { return this->transfer_byte(data); }
81 /// @brief Write one byte (MOSI only).
82 /// @param data Byte to send.
83 void spi_write(uint8_t data) override { this->write_byte(data); }
84 /// @brief Read one byte (MISO only).
85 /// @return Received byte.
86 uint8_t spi_read() override { return this->read_byte(); }
87
88 // --- YAML configuration setters (called by generated code) ---
89 /// Set the radio reset pin.
90 void set_rst_pin(InternalGPIOPin *pin) { this->rst_pin_ = pin; }
91 /// Set the DIO0 interrupt pin (SX1276).
92 void set_dio0_pin(InternalGPIOPin *pin) { this->dio0_pin_ = pin; }
93 /// Set the DIO4 preamble‑detect pin (SX1276, optional).
94 void set_dio4_pin(InternalGPIOPin *pin) { this->dio4_pin_ = pin; }
95 /// Set the DIO1 interrupt pin (SX1262).
96 void set_dio1_pin(InternalGPIOPin *pin) { this->dio1_pin_ = pin; }
97 /// Set the BUSY pin (SX1262).
98 void set_busy_pin(InternalGPIOPin *pin) { this->busy_pin_ = pin; }
99 /// Set the front‑end module enable pin.
100 void set_fem_en_pin(InternalGPIOPin *pin) { this->fem_en_pin_ = pin; }
101 /// Set the VFEM power pin.
102 void set_vfem_pin(InternalGPIOPin *pin) { this->vfem_pin_ = pin; }
103 /// Set the FEM PA switch pin.
104 void set_fem_pa_pin(InternalGPIOPin *pin) { this->fem_pa_pin_ = pin; }
105 /// Set the controller's node ID (hex string).
106 void set_node_id(const std::string &id) { this->node_id_str_ = id; }
107 /// Set the system key (hex string).
108 void set_system_key(const std::string &key) { this->system_key_str_ = key; }
109 /// Set transmit power (dBm).
110 void set_tx_power(uint8_t power) { this->tx_power_ = power; }
111 /// Set PA boost pin configuration.
112 void set_pa_pin(uint8_t pa_pin) { this->pa_pin_ = pa_pin; }
113 /// Set radio type ("sx1276" or "sx1262"); empty string means auto‑detect.
114 void set_radio_type(const std::string &type) { this->radio_type_ = type; }
115 /// Set TCXO voltage for SX1262 (1.8V / 3.3V).
116 void set_tcxo_voltage(uint8_t voltage) { this->tcxo_voltage_ = voltage; }
117
118 /// Declare that a remote (identified by its node ID) controls a registered device.
119 /// When activity from this remote is overheard, a status poll is scheduled for the device.
120 /// This is needed for 1W remotes whose destination address differs from the device's 2W ID.
121 /// @param remote_id Node ID of the remote control.
122 /// @param device_id Node ID of the device it controls.
123 void add_linked_remote(const std::string &remote_id, const std::string &device_id) {
124 this->linked_remotes_[remote_id].push_back(device_id);
125 }
126
127 // --- Device management (called by platform entities during setup) ---
128 /// Add a device to the registry by device ID only (legacy/delegating overload).
129 /// Type, subtype, and inverted default to UNKNOWN / 0 / false; use the 4-arg
130 /// overload when type/subtype/inverted come from YAML declarations.
131 /// @param device_id Hexadecimal node ID string.
132 virtual void add_device(const std::string &device_id);
133 /// Add a device to the registry with full metadata from YAML.
134 /// @param device_id Hexadecimal node ID string.
135 /// @param type Device type from YAML declaration (UNKNOWN if not specified).
136 /// @param subtype Device subtype from YAML declaration.
137 /// @param inverted Position inversion flag from YAML declaration.
138 virtual void add_device(const std::string &device_id, DeviceType type, uint8_t subtype, bool inverted);
139 /// Retrieve a device by ID; returns nullptr if not found.
140 /// @param device_id Hexadecimal node ID.
141 /// @return Pointer to IoDevice, or nullptr.
142 virtual IoDevice *get_device(const std::string &device_id);
143 /// Register a callback invoked when any device updates.
144 /// @param cb Callable with signature void(const std::string&, const IoDevice&).
145 virtual void register_device_callback(DeviceUpdateCallback cb) { this->callbacks_.push_back(std::move(cb)); }
146 /// Configure the optional follow-up polling interval for a registered device.
147 /// @param device_id Target device ID.
148 /// @param poll_interval_ms Poll interval in milliseconds; zero keeps the legacy one-shot settle poll only.
149 virtual void set_device_status_poll_interval(const std::string &device_id, uint32_t poll_interval_ms);
150
151 // --- High-level operations ---
152 /// Send a position command to a device.
153 /// @param device_id Target device ID.
154 /// @param position Desired position (0–100, or POS_STOP/POS_FAVORITE).
155 /// @return true if device acknowledged; false on timeout or radio error.
156 virtual bool set_device_position(const std::string &device_id, uint8_t position);
157 /// Send a tilt command to a tilt‑capable cover.
158 /// @param device_id Target device ID.
159 /// @param tilt_percent Desired tilt (0–100).
160 /// @return true if device acknowledged; false otherwise.
161 virtual bool set_device_tilt(const std::string &device_id, uint8_t tilt_percent);
162 /// Request current status from a device.
163 /// @param device_id Target device ID.
164 /// @return true if status frame was received and processed.
165 virtual bool request_device_status(const std::string &device_id);
166 /// Discover and pair a device that is in pairing mode.
167 /// @return true if pairing completed successfully; false otherwise.
168 virtual bool discover_and_pair();
169 /// Semantic binary helper for light entities. Internally mapped to the shared execute path.
170 /// @param device_id Target device ID.
171 /// @param on Desired on/off state.
172 /// @return true if device acknowledged.
173 virtual bool set_light_state(const std::string &device_id, bool on);
174 /// Semantic binary helper for switch entities. Internally mapped to the shared execute path.
175 /// @param device_id Target device ID.
176 /// @param on Desired on/off state.
177 /// @return true if device acknowledged.
178 virtual bool set_switch_state(const std::string &device_id, bool on);
179 /// Queue an async position update; returns immediately, executed in loop().
180 /// @param device_id Target device ID.
181 /// @param position Desired position.
182 virtual void queue_set_device_position(const std::string &device_id, uint8_t position);
183 /// Queue an async tilt update; returns immediately, executed in loop().
184 /// @param device_id Target device ID.
185 /// @param tilt_percent Desired tilt.
186 virtual void queue_set_device_tilt(const std::string &device_id, uint8_t tilt_percent);
187 /// Queue an async status request; returns immediately, executed in loop().
188 /// @param device_id Target device ID.
189 virtual void queue_request_device_status(const std::string &device_id);
190 /// Queue a pairing operation; executed in loop() when radio idle.
191 virtual void queue_discover_and_pair();
192 /// Async form of set_light_state() that keeps radio work serialized on the main loop.
193 /// @param device_id Target device ID.
194 /// @param on Desired on/off state.
195 virtual void queue_set_light_state(const std::string &device_id, bool on);
196 /// Async form of set_switch_state() that keeps radio work serialized on the main loop.
197 /// @param device_id Target device ID.
198 /// @param on Desired on/off state.
199 virtual void queue_set_switch_state(const std::string &device_id, bool on);
200
201 protected:
202 // --- Protocol-level operations ---
203 /// Transmit a raw IoFrame on the current frequency with given preamble length.
204 /// @param frame IoFrame to transmit.
205 /// @param freq RF frequency in Hz.
206 /// @param preamble Preamble length in bytes (LONG_PREAMBLE or SHORT_PREAMBLE).
207 bool transmit_frame_(const IoFrame &frame, uint32_t freq, uint16_t preamble);
208 /// Main request/response exchange with retry and automatic authentication.
209 /// @param request Outbound request IoFrame.
210 /// @param response Output: received response IoFrame.
211 /// @param freq RF frequency in Hz.
212 /// @return true if exchange succeeded; false otherwise.
213 bool send_and_receive_(const IoFrame &request, IoFrame &response, uint32_t freq);
214 /// Handle an inbound authenticated command from a device (status updates, etc.).
215 /// @param request Inbound authenticated request (e.g., CMD_STATUS_UPDATE).
216 /// @param freq RF frequency the packet arrived on.
217 /// @return true if authentication succeeded; false otherwise.
218 bool authenticate_request_(const IoFrame &request, uint32_t freq);
219 /// Parse received packet, update device state if it's a status frame, and notify covers.
220 /// @param packet Raw radio packet containing a parsed IoFrame.
221 void process_received_packet_(const RadioRxPacket &packet);
222 /// Extract position/status info from a status or status-update frame and merge into device record.
223 /// @param frame IoFrame containing a status-bearing command (CMD_PRIVATE_RESP or CMD_STATUS_UPDATE).
224 void update_device_status_(const IoFrame &frame);
225 /// Schedule a delayed status poll for a registered device using the Component timeout API.
226 /// @param device_id ID of the device to poll.
227 /// @param delay_ms Delay in milliseconds before polling.
228 /// @note Uses ESPHome's set_timeout() mechanism; the callback executes in loop().
229 /// A zero delay schedules immediately on the next loop iteration.
230 void schedule_status_poll_(const std::string &device_id, uint32_t delay_ms);
231 /// Begin bounded follow-up polling for a device after a command or overheard remote activity.
232 /// @param device_id ID of the device to poll.
233 /// @param initial_delay_ms Delay before the first follow-up poll.
234 void begin_status_poll_tracking_(const std::string &device_id, uint32_t initial_delay_ms);
235 /// Shared request/response helper for high-level operations.
236 /// @param device_id Target device ID.
237 /// @param request Outbound request frame.
238 /// @param warn_on_no_response If true, logs a warning when no response is received.
239 /// @param retry_after_fail_ms If non-zero, schedules next status poll after this delay on failure.
240 /// @return true if device acknowledged; false otherwise.
241 bool execute_request_and_update_(const std::string &device_id, const IoFrame &request, bool warn_on_no_response,
242 uint32_t retry_after_fail_ms = 0);
243 /// Fire all registered device update callbacks for the given device ID.
244 /// @param id Device ID that updated.
245 void notify_device_update_(const std::string &id);
246 /// Pop next pending operation from the queue and execute it (set position, request status, discover).
248
249 // --- Outbound exchange helpers ---
250 /// Wrap transmit_frame_ and mark context failed on error.
251 /// @param request Outbound IoFrame to transmit.
252 /// @param freq RF frequency in Hz.
253 /// @param preamble Preamble length in bytes.
254 /// @param ctx Exchange context (state updated on failure).
255 /// @return true if transmit succeeded; false otherwise.
256 bool transmit_request_(const IoFrame &request, uint32_t freq, uint16_t preamble,
258 /// Wait loop for the first response packet; classifies via decisions::classify_exchange_first_response.
259 /// @param request Original request frame (used for endpoint matching).
260 /// @param ctx Exchange context (provides deadline and receives rx frame on accept).
261 /// @return Disposition indicating next step.
264 /// Perform challenge-response (TX auth response) after a 0x3C is received.
265 /// @param request Original request frame (needed for HMAC derivation).
266 /// @param freq RF channel frequency (same channel used for the request).
267 /// @param ctx Exchange context holding the challenge frame and state.
268 /// @return true if challenge response was sent successfully; false otherwise.
269 bool handle_authentication_(const IoFrame &request, uint32_t freq, exchange::OutboundExchangeContext &ctx);
270 /// Wait loop for the final authenticated response; uses is_valid_final_response().
271 /// @param request Original request frame (used for endpoint matching).
272 /// @param ctx Exchange context (receives final rx frame on accept).
273 /// @return ACCEPT if a matching final response arrives; IGNORE_UNRELATED on timeout.
276
277 // --- Pairing helpers ---
278 /// Wait for a discovery response (0x29) during pairing.
279 /// @param timeout_ms Maximum time to wait in milliseconds.
280 /// @param packet Output: raw RadioRxPacket of the accepted frame.
281 /// @param response_frame Output: parsed IoFrame of the accepted frame.
282 /// @return PairingDiscoveryDisposition: ACCEPT on success; NO_RESPONSE or INVALID otherwise.
284 IoFrame &response_frame);
285 /// Wait for a key-challenge (0x3C) from target device during pairing key exchange.
286 /// @param timeout_ms Maximum time to wait in milliseconds.
287 /// @param packet Output: raw RadioRxPacket of the challenge frame.
288 /// @param challenge_frame Output: parsed IoFrame containing the challenge.
289 /// @param device_node_id Node ID of the device we are pairing (expected sender).
290 /// @return true if a valid challenge was received; false on timeout.
291 bool wait_for_key_challenge_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &challenge_frame,
292 const uint8_t device_node_id[NODE_ID_SIZE]);
293
294 /// Parse a discovery response frame into device metadata and ID.
295 /// @param frame Parsed discovery response.
296 /// @param device Output: populated IoDevice (node_id, type, subtype, inverted, position/target/stopped).
297 /// @param device_id Output: hex string representation of node ID.
298 static void parse_device_from_discovery(const IoFrame &frame, IoDevice &device, std::string &device_id);
299
300 // --- Pairing phase helpers ---
301 /// Phase 1: broadcast discovery (0x28) and wait for a device response (0x29).
302 /// @param context Pairing context modified on success.
303 /// @return PairingDiscoveryDisposition: ACCEPT, NO_RESPONSE, or INVALID.
305 /// Phase 2: authenticated key exchange (0x31 → 0x3C → 0x32 → 0x33).
306 /// @param context Pairing context populated by run_discovery_phase_().
307 /// @return true if key exchange completes successfully; false otherwise.
309 /// Phase 3: send SetConfig1 (0x6F) to finalize device configuration.
310 /// @param context Pairing context with device information.
311 /// @return true (pairing proceeds regardless of set‑config outcome).
313
314 /// @brief Type of queued pending operation for the main loop.
315 enum class PendingOperationType : uint8_t {
316 SET_POSITION, ///< Queue a set_device_position call (position 0–100 or special values).
317 SET_TILT, ///< Queue a set_device_tilt call (tilt percentage 0–100).
318 SET_LIGHT_STATE, ///< Queue a set_light_state call (binary on/off).
319 SET_SWITCH_STATE, ///< Queue a set_switch_state call (binary on/off).
320 REQUEST_STATUS, ///< Queue a request_device_status call (poll for current position).
321 DISCOVER_AND_PAIR, ///< Queue a discover_and_pair call (starts 3‑phase pairing flow).
322 };
323
324 /// @brief A single queued operation to be processed in loop().
326 PendingOperationType type; ///< Operation type (determines which queue handler to invoke).
327 std::string device_id; ///< Target device ID (hex string, e.g., "123ABC").
328 uint8_t position{0}; ///< Position/tilt value (0–100) or binary state (ON=0, OFF=100 for lights/switches).
329 };
330
331 /// @brief Debug snapshot of the last exchange attempt.
333 const char *stage{"idle"}; ///< Current stage name (e.g., "TX_REQUEST", "WAIT_FIRST_RESPONSE", "FAILED").
334 uint8_t tries{0}; ///< Try number (1‑based; increments on each retry within EXCHANGE_RETRY_COUNT).
335 uint8_t request_cmd{0}; ///< Command ID of the original request (e.g., CMD_EXECUTE=0x00).
336 bool saw_challenge{false}; ///< True if a challenge (0x3C) was seen during the exchange.
337 bool capture_valid{false}; ///< True if radio capture data is valid for the last packet seen.
338 bool capture_rx_done{false}; ///< True if RxDone interrupt fired (packet fully received).
339 bool capture_crc_error{false}; ///< True if CRC error flagged (SX1262 only; SX1276 IoHomeOn filters in hardware).
340 uint32_t capture_freq_hz{0}; ///< RF frequency of the captured packet (Hz).
341 uint16_t capture_irq_status{0}; ///< Raw IRQ status register value from the radio chip.
342 uint8_t capture_packet_status{0}; ///< Packet status byte (chip-specific; SX1262 includes CRC flag).
343 uint8_t capture_reported_len{0}; ///< Length reported by the radio's packet engine.
344 uint8_t capture_frame_len{0}; ///< Length of the parsed protocol frame after recovery/UART decoding.
345 int16_t capture_rssi_dbm{0}; ///< Received signal strength of the captured packet (dBm, negative).
346 };
347
348 void reset_exchange_debug_(uint8_t request_cmd);
349 void record_exchange_debug_(const char *stage, uint8_t tries, bool saw_challenge);
350 void log_exchange_debug_(const char *device_id) const;
351
352 // --- Frequency hopping ---
353 void hop_frequency_();
354
355 // --- Radio driver ---
357
358 // --- Hardware pins (set by YAML codegen, passed to radio driver in setup) ---
359 InternalGPIOPin *rst_pin_{nullptr};
360 InternalGPIOPin *dio0_pin_{nullptr}; ///< SX1276 DIO0 interrupt
361 InternalGPIOPin *dio4_pin_{nullptr}; ///< SX1276 DIO4 preamble detect (optional)
362 InternalGPIOPin *dio1_pin_{nullptr}; ///< SX1262 DIO1 interrupt
363 InternalGPIOPin *busy_pin_{nullptr}; ///< SX1262 BUSY pin
364 InternalGPIOPin *fem_en_pin_{nullptr}; ///< Front-end module enable
365 InternalGPIOPin *vfem_pin_{nullptr}; ///< Front-end module power
366 InternalGPIOPin *fem_pa_pin_{nullptr}; ///< Front-end module PA switch
367
368 // --- Configuration (from YAML) ---
369 std::string node_id_str_;
370 std::string system_key_str_;
371 std::string radio_type_; ///< "sx1276", "sx1262", or "" (auto-detect)
376 uint8_t tcxo_voltage_{DEFAULT_TCXO_VOLTAGE_SETTING_1P8V}; ///< SX1262 TCXO voltage setting (default 1.8 V)
377
378 // --- Runtime state ---
379 bool initialized_{false};
380 bool busy_{false};
381 uint32_t last_hop_us_{0};
383 std::map<std::string, IoDevice> devices_;
384 std::vector<DeviceUpdateCallback> callbacks_;
385 std::deque<PendingOperation> pending_operations_;
386 /// Maps remote node IDs to lists of device IDs they control.
387 /// Used to trigger status polls when 1W remote activity is overheard.
388 std::map<std::string, std::vector<std::string>> linked_remotes_;
389};
390
391// ============================================================================
392// Discover & Pair Button
393// ============================================================================
394
395/// Button entity that triggers device discovery and pairing when pressed in Home Assistant.
396class IOHomeDiscoverButton : public button::Button, public Component {
397 public:
398 void set_parent(IOHomeControlComponent *parent) { this->parent_ = parent; }
399 void dump_config() override {}
400
401 protected:
402 /// @brief When button is pressed, queue a discovery/pair operation.
403 void press_action() override { this->parent_->queue_discover_and_pair(); }
405};
406
407// ----------------------------------------------------------------------------
408// Test-visible helpers (inline for host unit tests)
409// ----------------------------------------------------------------------------
410
411/// Check if a stored node ID is valid (not all-zero, not all-0xFF).
412/// @param id 3‑byte node ID buffer.
413/// @return true if the ID is non-zero and non-0xFF.
414inline bool stored_node_id_is_valid(const uint8_t id[NODE_ID_SIZE]) {
415 bool all_zero = true;
416 bool all_ff = true;
417 for (uint8_t i = 0; i < NODE_ID_SIZE; i++) {
418 all_zero = all_zero && id[i] == 0;
419 all_ff = all_ff && id[i] == UINT8_MAX;
420 }
421 return !all_zero && !all_ff;
422}
423
424/// Format a position float as a human‑readable string (e.g. "50%", "unknown").
425/// @param pos Position value (0–100 or UNKNOWN_POSITION).
426/// @return String like "50%" or "unknown".
427inline std::string format_position(float pos) {
428 if (pos == UNKNOWN_POSITION) {
429 return "unknown";
430 }
432 snprintf(buf, sizeof(buf), "%.0f%%", pos);
433 return buf;
434}
435
436} // namespace home_io_control
437} // namespace esphome
The main IO-Homecontrol component.
Definition hub_core.h:60
InternalGPIOPin * fem_en_pin_
Front-end module enable.
Definition hub_core.h:364
InternalGPIOPin * fem_pa_pin_
Front-end module PA switch.
Definition hub_core.h:366
std::map< std::string, IoDevice > devices_
Definition hub_core.h:383
void set_tx_power(uint8_t power)
Set transmit power (dBm).
Definition hub_core.h:110
InternalGPIOPin * dio4_pin_
SX1276 DIO4 preamble detect (optional).
Definition hub_core.h:361
void reset_exchange_debug_(uint8_t request_cmd)
Definition hub_core.cpp:36
void set_node_id(const std::string &id)
Set the controller's node ID (hex string).
Definition hub_core.h:106
virtual bool set_device_tilt(const std::string &device_id, uint8_t tilt_percent)
Send a tilt command to a tilt‑capable cover.
InternalGPIOPin * dio1_pin_
SX1262 DIO1 interrupt.
Definition hub_core.h:362
bool send_and_receive_(const IoFrame &request, IoFrame &response, uint32_t freq)
Main request/response exchange with retry and automatic authentication.
virtual bool set_switch_state(const std::string &device_id, bool on)
Semantic binary helper for switch entities.
bool transmit_request_(const IoFrame &request, uint32_t freq, uint16_t preamble, exchange::OutboundExchangeContext &ctx)
Wrap transmit_frame_ and mark context failed on error.
void set_rst_pin(InternalGPIOPin *pin)
Set the radio reset pin.
Definition hub_core.h:90
decisions::PairingDiscoveryDisposition run_discovery_phase_(pairing::PairingContext &context)
Phase 1: broadcast discovery (0x28) and wait for a device response (0x29).
virtual void queue_set_device_tilt(const std::string &device_id, uint8_t tilt_percent)
Queue an async tilt update; returns immediately, executed in loop().
void begin_status_poll_tracking_(const std::string &device_id, uint32_t initial_delay_ms)
Begin bounded follow-up polling for a device after a command or overheard remote activity.
void dump_config() override
Dump configuration and radio debug info to the log.
Definition hub_core.cpp:305
virtual void register_device_callback(DeviceUpdateCallback cb)
Register a callback invoked when any device updates.
Definition hub_core.h:145
void set_pa_pin(uint8_t pa_pin)
Set PA boost pin configuration.
Definition hub_core.h:112
void record_exchange_debug_(const char *stage, uint8_t tries, bool saw_challenge)
Definition hub_core.cpp:41
virtual IoDevice * get_device(const std::string &device_id)
Retrieve a device by ID; returns nullptr if not found.
Definition hub_core.cpp:256
virtual void queue_request_device_status(const std::string &device_id)
Queue an async status request; returns immediately, executed in loop().
void spi_enable() override
Enable the SPI bus.
Definition hub_core.h:74
virtual void add_device(const std::string &device_id)
Add a device to the registry by device ID only (legacy/delegating overload).
Definition hub_core.cpp:237
void set_fem_en_pin(InternalGPIOPin *pin)
Set the front‑end module enable pin.
Definition hub_core.h:100
InternalGPIOPin * vfem_pin_
Front-end module power.
Definition hub_core.h:365
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.
Definition hub_core.cpp:230
void process_pending_operation_()
Pop next pending operation from the queue and execute it (set position, request status,...
void schedule_status_poll_(const std::string &device_id, uint32_t delay_ms)
Schedule a delayed status poll for a registered device using the Component timeout API.
void hop_frequency_()
Hop to the next channel in the 3‑channel sequence: CH1 → CH2 → CH3 → CH1.
Definition hub_core.cpp:169
void add_linked_remote(const std::string &remote_id, const std::string &device_id)
Declare that a remote (identified by its node ID) controls a registered device.
Definition hub_core.h:123
bool finalize_pairing_configuration_(pairing::PairingContext &context)
Phase 3: send SetConfig1 (0x6F) to finalize device configuration.
bool execute_request_and_update_(const std::string &device_id, const IoFrame &request, bool warn_on_no_response, uint32_t retry_after_fail_ms=0)
Shared request/response helper for high-level operations.
void set_dio1_pin(InternalGPIOPin *pin)
Set the DIO1 interrupt pin (SX1262).
Definition hub_core.h:96
decisions::ExchangeFirstResponseDisposition wait_for_first_response_(const IoFrame &request, exchange::OutboundExchangeContext &ctx)
Wait loop for the first response packet; classifies via decisions::classify_exchange_first_response.
PendingOperationType
Type of queued pending operation for the main loop.
Definition hub_core.h:315
@ SET_TILT
Queue a set_device_tilt call (tilt percentage 0–100).
Definition hub_core.h:317
@ SET_LIGHT_STATE
Queue a set_light_state call (binary on/off).
Definition hub_core.h:318
@ SET_SWITCH_STATE
Queue a set_switch_state call (binary on/off).
Definition hub_core.h:319
@ DISCOVER_AND_PAIR
Queue a discover_and_pair call (starts 3‑phase pairing flow).
Definition hub_core.h:321
@ REQUEST_STATUS
Queue a request_device_status call (poll for current position).
Definition hub_core.h:320
@ SET_POSITION
Queue a set_device_position call (position 0–100 or special values).
Definition hub_core.h:316
void set_radio_type(const std::string &type)
Set radio type ("sx1276" or "sx1262"); empty string means auto‑detect.
Definition hub_core.h:114
void loop() override
Main loop: process pending operations and drive radio state machine.
Definition hub_core.cpp:263
void log_exchange_debug_(const char *device_id) const
Definition hub_core.cpp:58
bool wait_for_key_challenge_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &challenge_frame, const uint8_t device_node_id[NODE_ID_SIZE])
Wait for a key-challenge (0x3C) from target device during pairing key exchange.
decisions::ExchangeFinalResponseDisposition wait_for_final_response_(const IoFrame &request, exchange::OutboundExchangeContext &ctx)
Wait loop for the final authenticated response; uses is_valid_final_response().
std::string radio_type_
"sx1276", "sx1262", or "" (auto-detect)
Definition hub_core.h:371
std::vector< DeviceUpdateCallback > callbacks_
Definition hub_core.h:384
float get_setup_priority() const override
Get setup priority (HARDWARE to initialize early).
Definition hub_core.h:70
bool run_key_exchange_phase_(pairing::PairingContext &context)
Phase 2: authenticated key exchange (0x31 → 0x3C → 0x32 → 0x33).
void set_busy_pin(InternalGPIOPin *pin)
Set the BUSY pin (SX1262).
Definition hub_core.h:98
std::deque< PendingOperation > pending_operations_
Definition hub_core.h:385
virtual bool discover_and_pair()
Discover and pair a device that is in pairing mode.
uint8_t spi_read() override
Read one byte (MISO only).
Definition hub_core.h:86
bool transmit_frame_(const IoFrame &frame, uint32_t freq, uint16_t preamble)
Transmit a raw IoFrame on the current frequency with given preamble length.
Definition hub_core.cpp:194
void set_dio0_pin(InternalGPIOPin *pin)
Set the DIO0 interrupt pin (SX1276).
Definition hub_core.h:92
decisions::PairingDiscoveryDisposition wait_for_discovery_response_(uint32_t timeout_ms, RadioRxPacket &packet, IoFrame &response_frame)
Wait for a discovery response (0x29) during pairing.
void set_vfem_pin(InternalGPIOPin *pin)
Set the VFEM power pin.
Definition hub_core.h:102
void update_device_status_(const IoFrame &frame)
Extract position/status info from a status or status-update frame and merge into device record.
virtual bool set_light_state(const std::string &device_id, bool on)
Semantic binary helper for light entities.
InternalGPIOPin * dio0_pin_
SX1276 DIO0 interrupt.
Definition hub_core.h:360
void set_system_key(const std::string &key)
Set the system key (hex string).
Definition hub_core.h:108
virtual void queue_set_light_state(const std::string &device_id, bool on)
Async form of set_light_state() that keeps radio work serialized on the main loop.
void spi_write(uint8_t data) override
Write one byte (MOSI only).
Definition hub_core.h:83
void set_tcxo_voltage(uint8_t voltage)
Set TCXO voltage for SX1262 (1.8V / 3.3V).
Definition hub_core.h:116
bool handle_authentication_(const IoFrame &request, uint32_t freq, exchange::OutboundExchangeContext &ctx)
Perform challenge-response (TX auth response) after a 0x3C is received.
void notify_device_update_(const std::string &id)
Fire all registered device update callbacks for the given device ID.
Definition hub_core.cpp:220
virtual void queue_set_device_position(const std::string &device_id, uint8_t position)
Queue an async position update; returns immediately, executed in loop().
virtual void queue_set_switch_state(const std::string &device_id, bool on)
Async form of set_switch_state() that keeps radio work serialized on the main loop.
void set_dio4_pin(InternalGPIOPin *pin)
Set the DIO4 preamble‑detect pin (SX1276, optional).
Definition hub_core.h:94
virtual bool set_device_position(const std::string &device_id, uint8_t position)
Send a position command to a device.
std::map< std::string, std::vector< std::string > > linked_remotes_
Maps remote node IDs to lists of device IDs they control.
Definition hub_core.h:388
void setup() override
Initialize hardware (radio and device registry).
Definition hub_core.cpp:87
uint8_t spi_transfer(uint8_t data) override
Transfer one byte full‑duplex.
Definition hub_core.h:80
uint8_t tcxo_voltage_
SX1262 TCXO voltage setting (default 1.8 V).
Definition hub_core.h:376
void process_received_packet_(const RadioRxPacket &packet)
Parse received packet, update device state if it's a status frame, and notify covers.
bool authenticate_request_(const IoFrame &request, uint32_t freq)
Handle an inbound authenticated command from a device (status updates, etc.).
void spi_disable() override
Disable the SPI bus.
Definition hub_core.h:76
InternalGPIOPin * busy_pin_
SX1262 BUSY pin.
Definition hub_core.h:363
virtual void queue_discover_and_pair()
Queue a pairing operation; executed in loop() when radio idle.
static void parse_device_from_discovery(const IoFrame &frame, IoDevice &device, std::string &device_id)
Parse a discovery response frame into device metadata and ID.
virtual bool request_device_status(const std::string &device_id)
Request current status from a device.
void set_fem_pa_pin(InternalGPIOPin *pin)
Set the FEM PA switch pin.
Definition hub_core.h:104
Button entity that triggers device discovery and pairing when pressed in Home Assistant.
Definition hub_core.h:396
void press_action() override
When button is pressed, queue a discovery/pair operation.
Definition hub_core.h:403
void set_parent(IOHomeControlComponent *parent)
Definition hub_core.h:398
Abstract radio driver for IO-Homecontrol.
Interface for SPI bus access.
Pure transition helpers for hub-owned exchange and pairing frame decisions.
Internal exchange-state model for hub-owned authenticated non‑pairing flows.
Internal pairing-state model for hub‑owned discovery and key‑exchange flows.
PairingDiscoveryDisposition
Disposition during pairing discovery phase.
ExchangeFinalResponseDisposition
Disposition for the final response after authentication.
ExchangeFirstResponseDisposition
Disposition for the first response in an authenticated exchange.
static constexpr float UNKNOWN_POSITION
Sentinel value meaning "position is not known yet".
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_frame.h:80
DeviceType
Device type identifiers reported by IO‑Homecontrol products.
constexpr size_t POSITION_TEXT_BUFFER_SIZE
Buffer for formatted position strings such as "100%".
Definition hub_core.h:46
std::function< void(const std::string &device_id, const IoDevice &device)> DeviceUpdateCallback
Callback type for notifying covers of device state changes.
Definition hub_core.h:41
std::string format_position(float pos)
Format a position float as a human‑readable string (e.g.
Definition hub_core.h:427
bool stored_node_id_is_valid(const uint8_t id[NODE_ID_SIZE])
Check if a stored node ID is valid (not all-zero, not all-0xFF).
Definition hub_core.h:414
constexpr uint8_t DEFAULT_TCXO_VOLTAGE_SETTING_1P8V
SX1262 DIO3 setting value for a 1.8 V TCXO.
Definition hub_core.h:45
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_frame.h:83
constexpr uint8_t DEFAULT_PA_PIN_PA_BOOST
SX1276 PA_CONFIG selector for the PA_BOOST output path.
Definition hub_core.h:44
constexpr uint8_t DEFAULT_TX_POWER_DBM
Default TX power used unless YAML overrides it.
Definition hub_core.h:43
IO-Homecontrol 2W protocol definitions.
Radio abstraction layer for IO-Homecontrol.
Debug snapshot of the last exchange attempt.
Definition hub_core.h:332
uint8_t request_cmd
Command ID of the original request (e.g., CMD_EXECUTE=0x00).
Definition hub_core.h:335
uint8_t tries
Try number (1‑based; increments on each retry within EXCHANGE_RETRY_COUNT).
Definition hub_core.h:334
bool capture_rx_done
True if RxDone interrupt fired (packet fully received).
Definition hub_core.h:338
const char * stage
Current stage name (e.g., "TX_REQUEST", "WAIT_FIRST_RESPONSE", "FAILED").
Definition hub_core.h:333
uint8_t capture_frame_len
Length of the parsed protocol frame after recovery/UART decoding.
Definition hub_core.h:344
uint8_t capture_reported_len
Length reported by the radio's packet engine.
Definition hub_core.h:343
uint16_t capture_irq_status
Raw IRQ status register value from the radio chip.
Definition hub_core.h:341
bool capture_crc_error
True if CRC error flagged (SX1262 only; SX1276 IoHomeOn filters in hardware).
Definition hub_core.h:339
uint8_t capture_packet_status
Packet status byte (chip-specific; SX1262 includes CRC flag).
Definition hub_core.h:342
int16_t capture_rssi_dbm
Received signal strength of the captured packet (dBm, negative).
Definition hub_core.h:345
bool capture_valid
True if radio capture data is valid for the last packet seen.
Definition hub_core.h:337
bool saw_challenge
True if a challenge (0x3C) was seen during the exchange.
Definition hub_core.h:336
uint32_t capture_freq_hz
RF frequency of the captured packet (Hz).
Definition hub_core.h:340
A single queued operation to be processed in loop().
Definition hub_core.h:325
std::string device_id
Target device ID (hex string, e.g., "123ABC").
Definition hub_core.h:327
PendingOperationType type
Operation type (determines which queue handler to invoke).
Definition hub_core.h:326
uint8_t position
Position/tilt value (0–100) or binary state (ON=0, OFF=100 for lights/switches).
Definition hub_core.h:328
Runtime state of a paired IO‑Homecontrol device.
Parsed IO‑Homecontrol frame (CTRL0/1 + addresses + command + data).
Raw packet received from the radio.
Context carried across one outbound authenticated exchange.
Context object that lives for the duration of a single pairing attempt.
Definition hub_pairing.h:54