Skip to content

Command Transmitter sendCommand

Matthias Jobst edited this page May 29, 2026 · 1 revision

SomfyCommandTransmitter::sendCommand — Flowchart

Documents the decision logic of SomfyCommandTransmitter::sendCommand, the per-shade command dispatcher that translates a logical somfy_commands value into the correct raw RF frame (via SomfyRemote::sendCommand) and updates the shade's optimistic position/tilt targets.

Class name note: the file/class is SomfyCommandTransmitter (not SomfyCommandTransceiver).

Overloads

void sendCommand(somfy_commands cmd);                                  // → sendCommand(cmd, shade->repeats)
void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);

The single-argument overload simply forwards to the full overload using the shade's default repeats count. All the logic below lives in the full overload.

What the targets mean

Several branches set p_target / p_tiltTarget / p_currentPos. These are optimistic state updates — the shade assumes the motor will honour the command, so the movement tracker and HomeKit/web feedback reflect the new intent immediately (0.0 = fully open, 100.0 = fully closed in the internal convention).

Flowchart

flowchart TD
    A["sendCommand(cmd, repeat, stepSize)"] --> B{"bitLength == 0?"}
    B -- yes --> B1["bitLength = transceiver.config.type"]
    B -- no --> C
    B1 --> C{"cmd?"}

    %% ---------- UP ----------
    C -- Up --> U{"tiltType == euromode?"}
    U -- yes --> U1["SomfyRemote::sendCommand(Up, TILT_REPEATS)<br/>target = 0"]
    U -- no --> U2["SomfyRemote::sendCommand(Up, repeat)"]
    U2 --> U3{"tiltType?"}
    U3 -- tiltonly --> U3a["target = 100<br/>tiltTarget = 0<br/>currentPos = 100"]
    U3 -- integrated --> U3b["target = 0<br/>tiltTarget = 0"]
    U3 -- other --> U3c["target = 0"]

    %% ---------- DOWN ----------
    C -- Down --> D{"tiltType == euromode?"}
    D -- yes --> D1["SomfyRemote::sendCommand(Down, TILT_REPEATS)<br/>target = 100"]
    D -- no --> D2["SomfyRemote::sendCommand(Down, repeat)"]
    D2 --> D3{"tiltType?"}
    D3 -- tiltonly --> D3a["target = 100<br/>tiltTarget = 100<br/>currentPos = 100"]
    D3 -- integrated --> D3b["target = 100<br/>tiltTarget = 100"]
    D3 -- other --> D3c["target = 100"]

    %% ---------- MY ----------
    C -- My --> M{"isToggle() ||<br/>shadeType == drycontact?"}
    M -- yes --> M1["SomfyRemote::sendCommand(My, repeat)"]
    M -- no --> M2{"shadeType == drycontact2?"}
    M2 -- yes --> M2a["return (no-op)"]
    M2 -- no --> M3{"isIdle()?"}
    M3 -- yes --> M3a["moveToMyPosition()<br/>return"]
    M3 -- no --> M4["SomfyRemote::sendCommand(My, repeat)<br/>if tiltType != tiltonly: target = currentPos<br/>tiltTarget = currentTiltPos"]

    %% ---------- TOGGLE ----------
    C -- Toggle --> T{"bitLength != 80?"}
    T -- yes --> T1["SomfyRemote::sendCommand(My, repeat, stepSize)"]
    T -- no --> T2["SomfyRemote::sendCommand(Toggle, repeat)"]

    %% ---------- PROG (toggle shades) ----------
    C -- "Prog (and isToggle())" --> P1["SomfyRemote::sendCommand(Toggle, repeat, stepSize)"]

    %% ---------- DEFAULT ----------
    C -- "other / Prog (non-toggle)" --> X1["SomfyRemote::sendCommand(cmd, repeat, stepSize)"]

    %% ---------- terminal styling ----------
    U1 & U3a & U3b & U3c & D1 & D3a & D3b & D3c & M1 & M2a & M3a & M4 & T1 & T2 & P1 & X1 --> Z(["frame queued to RMT TX<br/>(or no-op / delegated)"])
Loading

Branch reference

cmd Condition Raw frame sent Optimistic state set
Up tiltType == euromode Up, TILT_REPEATS target = 0
Up tiltType == tiltonly Up, repeat target = 100, tiltTarget = 0, currentPos = 100
Up tiltType == integrated Up, repeat target = 0, tiltTarget = 0
Up otherwise Up, repeat target = 0
Down tiltType == euromode Down, TILT_REPEATS target = 100
Down tiltType == tiltonly Down, repeat target = 100, tiltTarget = 100, currentPos = 100
Down tiltType == integrated Down, repeat target = 100, tiltTarget = 100
Down otherwise Down, repeat target = 100
My isToggle() or drycontact My, repeat
My drycontact2 none — early return
My isIdle() none — delegates to moveToMyPosition() then return (set by moveToMyPosition)
My otherwise (moving) My, repeat (acts as Stop) target = currentPos (unless tiltonly), tiltTarget = currentTiltPos
Toggle bitLength != 80 My, repeat, stepSize
Toggle bitLength == 80 Toggle, repeat
Prog isToggle() Toggle, repeat, stepSize
any other cmd, repeat, stepSize (pass-through)

Notable behaviours

  • My is overloaded. On an idle shade it means "go to the My/favourite position" (moveToMyPosition); on a moving shade the same My frame acts as Stop, so the optimistic target is snapped to the current position to halt the movement tracker.
  • drycontact2 ignores My entirely (early return, no frame).
  • Toggle vs bitLength. For non-80-bit protocols a Toggle is sent as a My frame; only true 80-bit remotes emit a real Toggle command. The same dual mapping is why Prog on a toggle-type shade is rewritten to Toggle.
  • Tilt types (tilt_types, SomfyFrame.h:66): none, tiltmotor, integrated, tiltonly, euromode. Euromode and tilt-only shades have distinct position/tilt semantics, which is why Up/Down fan out.
  • This method only dispatches the frame to SomfyRemote::sendCommand, which itself is non-blocking and skips silently when the RMT TX is busy — see HomeKit Command Sequence for the surrounding queue/transmit flow.

Source

Clone this wiki locally