Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
esphome::home_io_control::OneWayTransmitter Class Reference

Sends 1W commands as the repeated bursts real remotes send. More...

#include <oneway_transmitter.h>

Collaboration diagram for esphome::home_io_control::OneWayTransmitter:

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.

Detailed Description

Sends 1W commands as the repeated bursts real remotes send.

Definition at line 82 of file oneway_transmitter.h.

Constructor & Destructor Documentation

◆ OneWayTransmitter()

esphome::home_io_control::OneWayTransmitter::OneWayTransmitter ( OneWayTransmitFn transmit,
const TuningConfig * tuning )
inline
Parameters
transmitHow to put a frame on air; must stay valid for this object's lifetime.
tuningHub'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.

Member Function Documentation

◆ add_identity()

void esphome::home_io_control::OneWayTransmitter::add_identity ( const OneWayControllerIdentity & identity)
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.

Parameters
identityFully-resolved identity (address and key already decided at schema time).

Definition at line 99 of file oneway_transmitter.h.

◆ identities()

const OneWayControllerRegistry & esphome::home_io_control::OneWayTransmitter::identities ( ) const
inlinenodiscard
Returns
The configured controller identities.

Definition at line 106 of file oneway_transmitter.h.

◆ send_burst()

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.

Parameters
frameSigned 1W frame to send.
power_classWhich preamble/CTRL1 shape each copy gets (ADR 0038).
Returns
true if at least one copy reached the radio. Partial success is still reported as success because it is genuinely what the caller wants to know — with no reply frame, "some copies went out" is the most any layer here can ever establish, and a device needs only one of them.

Definition at line 262 of file oneway_transmitter.cpp.

Here is the call graph for this function:

◆ send_command()

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.

Parameters
controller_idYAML handle of the controller identity to transmit as.
cmdNamed command (STOP, FAVORITE, VENT). CoverCommand::FORCE_OPEN has no 1W encoding and cannot be built — see create_1w_execute_command() (proto_commands.h).
Returns
true if at least one copy reached the radio; false if the identity is unknown, the sequence could not be reserved, or the frame could not be built.

Definition at line 111 of file oneway_transmitter.cpp.

Here is the call graph for this function:

◆ send_enrollment()

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).

Parameters
controller_idYAML handle of the controller identity to register.
Returns
true if the credential frame(s) that actually register this identity reached the radio — the 0x30 (SOMFY) or the sweep (VELUX_KLI). The VELUX STOP+DOWN follow-up is skipped entirely if the sweep transmitted nothing; a failed 0x39 prelude, or a partial STOP/DOWN after a good sweep, only logs and does not flip this.

Definition at line 128 of file oneway_transmitter.cpp.

Here is the call graph for this function:

◆ send_position()

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.

Parameters
controller_idYAML handle of the controller identity to transmit as.
positionTarget position 0–100 (0 = fully open, 100 = fully closed).
Returns
true if at least one copy reached the radio.

Definition at line 119 of file oneway_transmitter.cpp.

Here is the call graph for this function:

◆ send_unenrollment()

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.

Warning
Unconfirmed standalone on real hardware. Firing 0x39 alone (outside the enrollment handshake) has had no observable effect on this project's test hardware; the leading hypothesis is that it needs the same association-mode window enrollment does. See ADR 0026 § Consequences.
Parameters
controller_idYAML handle of the controller identity to remove.
Returns
true if at least one copy reached the radio.

Definition at line 252 of file oneway_transmitter.cpp.

Here is the call graph for this function:

◆ set_command_report_callback()

void esphome::home_io_control::OneWayTransmitter::set_command_report_callback ( OneWayCommandReportFn callback)
inline

Register the callback that receives a report after every command attempt.

Parameters
callbackInvoked 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.

◆ setup()

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.


The documentation for this class was generated from the following files: