|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Sends 1W commands as the repeated bursts real remotes send. More...
#include <oneway_transmitter.h>
Public Member Functions | |
| OneWayTransmitter (OneWayTransmitFn transmit, const TuningConfig *tuning) | |
| void | add_identity (const OneWayControllerIdentity &identity) |
| Register a configured controller identity. | |
| void | setup () |
| Open each registered identity's persistent sequence counter. | |
| const OneWayControllerRegistry & | identities () const |
| void | set_command_report_callback (OneWayCommandReportFn callback) |
| Register the callback that receives a report after every command attempt. | |
| bool | send_command (const std::string &controller_id, CoverCommand cmd) |
| Send a named command as the identity's controller. | |
| bool | send_position (const std::string &controller_id, uint8_t position) |
| Send a numeric position as the identity's controller. | |
| bool | send_burst (const IoFrame &frame, OneWayPowerClass power_class) |
| Transmit one already-built, already-signed 1W frame as a burst. | |
| bool | send_enrollment (const std::string &controller_id) |
| Register this identity as a controller on every device currently in association mode (the receiver's own association-mode gesture, ADR 0026: a multi-second PROG hold on a Somfy actuator, or a ~1 s Gear press on an already-registered VELUX control followed by the product's own ready sequence), using the gesture its manufacturer expects (resolve_oneway_wire_profile(), ADR 0032). | |
| bool | send_unenrollment (const std::string &controller_id) |
| Un-register this identity from every device of its class currently in association mode (CMD 0x39) alone — also the prelude send_enrollment() fires before its own 0x30. | |
Sends 1W commands as the repeated bursts real remotes send.
Definition at line 82 of file oneway_transmitter.h.
|
inline |
| transmit | How to put a frame on air; must stay valid for this object's lifetime. |
| tuning | Hub's live TuningConfig; must outlive this object. Read per burst (never cached) so a live change to normal_start_preamble (the Home Assistant number entity) takes effect on the next command without a reboot – the same pattern ExchangeEngine already uses for the identical field. |
Definition at line 89 of file oneway_transmitter.h.
|
inline |
Register a configured controller identity.
Called once per oneway_controllers: entry.
Config only — it does not touch persistent storage, because generated wiring runs before preferences are usable. setup() is what opens each identity's counter.
| identity | Fully-resolved identity (address and key already decided at schema time). |
Definition at line 99 of file oneway_transmitter.h.
|
inlinenodiscard |
Definition at line 106 of file oneway_transmitter.h.
| bool esphome::home_io_control::OneWayTransmitter::send_burst | ( | const IoFrame & | frame, |
| OneWayPowerClass | power_class ) |
Transmit one already-built, already-signed 1W frame as a burst.
Sends the frame ONEWAY_BURST_REPEATS times, ONEWAY_BURST_INTERVAL_MS apart, on FREQ_CH2. power_class decides each copy's preamble and CTRL1 via oneway_burst_copy_shape() (ADR 0038); a required parameter, never defaulted — a default here would silently reintroduce a hard-coded preamble at a new call site, exactly the mistake ADR 0029 records.
It retransmits identical bytes, except CTRL1 and the preamble. The sequence and the MAC were fixed by the caller before this was called, and every copy carries them unchanged: a device treats one sequence as one command, so four copies bearing four sequences are four commands, of which it will accept one and reject three as replays. This function therefore never rebuilds the frame's cmd/data/sequence/MAC and never touches a sequence counter — it only sets or clears CTRL1_LOW_POWER per copy (clearing it too, whatever the builder produced: the transmitter owns this bit unconditionally) and picks that copy's preamble. This is safe because the 1W MAC span covers only cmd+data (never CTRL1) and the CRC is computed per transmission by the driver, so neither authenticates or depends on CTRL1.
It blocks for the whole burst, feeding the watchdog in the gaps. The three inter-copy gaps alone are 3 * ONEWAY_BURST_INTERVAL_MS = ~120 ms of pure delay; how much airtime the four copies themselves add depends on power_class: LEGACY_LONG puts LONG_PREAMBLE on every copy, ≈1.0–1.2 s per burst total (measured on SX1276, issue #74's logs: ~1.2 s); ALWAYS_ALIVE puts the live normal_start_preamble on every copy, ≈200 ms per burst (measured on SX1276, issue #74); LOW_POWER sits between the two (one long copy, three normal). Per ADR 0013 all radio work happens on the ESPHome loop and the operation queue is the concurrency model; an authenticated 2W exchange already blocks far longer than this. Scheduling the repeats through a timeout would add a second concurrency model and would let a queued 2W exchange interleave between copies of one command.
| frame | Signed 1W frame to send. |
| power_class | Which preamble/CTRL1 shape each copy gets (ADR 0038). |
Definition at line 262 of file oneway_transmitter.cpp.
| bool esphome::home_io_control::OneWayTransmitter::send_command | ( | const std::string & | controller_id, |
| CoverCommand | cmd ) |
Send a named command as the identity's controller.
Resolves the identity, reserves exactly one sequence for the whole command, builds and signs the frame with that identity's key, and bursts it.
Addresses a device class, not a device. Every device of io_device_type in range that holds the signing key acts on it — that is what 1W is, not a limitation to work around. Two devices of one class are separable only if they can be given separate identities.
| controller_id | YAML handle of the controller identity to transmit as. |
| cmd | Named command (STOP, FAVORITE, VENT). CoverCommand::FORCE_OPEN has no 1W encoding and cannot be built — see create_1w_execute_command() (proto_commands.h). |
Definition at line 111 of file oneway_transmitter.cpp.
| bool esphome::home_io_control::OneWayTransmitter::send_enrollment | ( | const std::string & | controller_id | ) |
Register this identity as a controller on every device currently in association mode (the receiver's own association-mode gesture, ADR 0026: a multi-second PROG hold on a Somfy actuator, or a ~1 s Gear press on an already-registered VELUX control followed by the product's own ready sequence), using the gesture its manufacturer expects (resolve_oneway_wire_profile(), ADR 0032).
EnrollGesture::SOMFY (somfy / unset / any unprofiled vendor): 0x39 (remove, self-directed) then 0x30 (add) — the documented 1W handshake (the iown-homecontrol link-layer notes), both to the identity's own io_device_type, one burst each, matched by a real Smoove capture landing the two 128 ms apart (tests/corpus/captures/enrollment/somfy_smoove_enrollment_add_and_remove_controller_sx1276.yaml).
EnrollGesture::VELUX_KLI (manufacturer velux): 0x39 to the all-devices address, then a 0x30 burst to each class in effective_enrollment_classes() under one shared sequence, then a STOP and a DOWN EXECUTE to the all-devices address at the VELUX ACEI — the KLI manual's STOP-then-DOWN registration completion. Matches the issue #74 KLI 310 capture and has enrolled a VELUX SML roller shutter and KLI 312 interior blinds on real hardware (the closing DOWN is the visible success signal, unless the cover already sits fully closed). The STOP+DOWN frames themselves are not matched against a VELUX capture (tests/corpus/captures/enrollment/synthetic_enrollment_velux_kli_prog_sweep.yaml).
The 0x30's MAC trailer is configurable via enrollment_with_mac: (default false, no MAC — see create_1w_add_controller()'s @warning). Real VELUX (#74) and real Somfy captures both use the no-MAC form; a real Izymo has separately accepted the MAC-bearing form too.
Blocks for the whole gesture feeding the watchdog in the gaps. The VELUX path is 6 bursts (0x39 + 3-class 0x30 sweep + STOP + DOWN); with the identity's power class unset (legacy), each burst carries LONG_PREAMBLE on every copy, ≈6–7 s total (SX1276 logs in issue #74 measured ~7.4 s, ~10.6 s with enrollment_with_mac: true); ~1.2 s with low_power: false (ADR 0038, measured on SX1276 in issue #74). This is a user-initiated, once-per-device action, the same shape as the pairing button (pairing_discovery_wait_ms → 5000).
| controller_id | YAML handle of the controller identity to register. |
Definition at line 128 of file oneway_transmitter.cpp.
| bool esphome::home_io_control::OneWayTransmitter::send_position | ( | const std::string & | controller_id, |
| uint8_t | position ) |
Send a numeric position as the identity's controller.
Same contract as send_command(). Every position 0–100 is ordinary; none is a special code.
| controller_id | YAML handle of the controller identity to transmit as. |
| position | Target position 0–100 (0 = fully open, 100 = fully closed). |
Definition at line 119 of file oneway_transmitter.cpp.
| bool esphome::home_io_control::OneWayTransmitter::send_unenrollment | ( | const std::string & | controller_id | ) |
Un-register this identity from every device of its class currently in association mode (CMD 0x39) alone — also the prelude send_enrollment() fires before its own 0x30.
Reachable directly through the explicitly-named oneway_remove_controller native API action, for un-enrolling without immediately re-enrolling.
| controller_id | YAML handle of the controller identity to remove. |
Definition at line 252 of file oneway_transmitter.cpp.
|
inline |
Register the callback that receives a report after every command attempt.
| callback | Invoked once per logical command, including failed ones — a command that never left the hub is exactly the case a user needs to see, and 1W will not tell them. |
Definition at line 111 of file oneway_transmitter.h.
| void esphome::home_io_control::OneWayTransmitter::setup | ( | ) |
Open each registered identity's persistent sequence counter.
Call once from the hub's setup(), never from generated wiring.
Definition at line 39 of file oneway_transmitter.cpp.