Skip to content

Local channel

Direct access to the box on the LAN (TCP port 400). See the local channels protocol notes for the frame format.

client

LAN channel to the Daisy box (DaisyApplication#sendDM).

One TCP connection per command to <box ip>:400: write the obfuscated frame (no newline), read one plaintext line. A line containing "ACK" means the box accepted the command. The box's IP is only known through the cloud status item NET_PARAM of the box device, or can be configured by the user.

This channel only sends commands: device state is never readable locally.

LocalClient

LocalClient(
    host: str,
    *,
    port: int = LOCAL_PORT,
    connect_timeout: float = LOCAL_CONNECT_TIMEOUT,
)

Send commands to a Daisy box on the local network.

host instance-attribute

host = host

port instance-attribute

port = port

connect_timeout instance-attribute

connect_timeout = connect_timeout

send async

send(
    inst_code: str,
    commands: list[WireCommand],
    *,
    scenario_id: int = 0,
) -> str

Send commands; return the box's reply line. Raises on failure or refusal.

diagnostic async

diagnostic() -> str

Read the box's diagnostic log (comma-separated 10-char entries).

exchange async

exchange(frame: str, *, read_timeout: float) -> str | None

Send one frame and read one line; None if the reply timed out.

crypto

Obfuscation used on the Daisy box LAN channel (TCP port 400).

Port of DaisyApplication#encrypt:

  1. pick r in 0..27 and rotate the 28-char key left by r;
  2. take the epoch seconds as a 10-digit string ts and turn each pair of digits into one character (chr(int("12")) == "\x0c");
  3. plaintext = chr(ts[0:2]) + message, with chr(ts[2:4]), chr(ts[4:6]) and chr(ts[6:8]) inserted before the characters at indexes 50, 70 and 90 of the original message (only if the message is long enough), + chr(ts[8:10]);
  4. XOR every UTF-16 code unit with the rotated key, encode as UTF-8, Base64 it;
  5. prefix "S" (scenario) or "M" (anything else) and ALPHABET[r].

The box answers with a plaintext line; a line containing "ACK" means success.

KEY module-attribute

KEY: Final = 'd3Cr1pTam1St0CaZzOSe61nGrad0'

ALPHABET module-attribute

ALPHABET: Final = 'abcdefghijklmnopqrstuvwxyz0123456789'

encrypt

encrypt(
    message: str,
    *,
    scenario: bool = False,
    rotation: int | None = None,
    timestamp: int | None = None,
) -> str

Obfuscate message the way the app does before sending it to port 400.

rotation and timestamp are only meant for tests; by default they are random and the current time, like the app.

decrypt

decrypt(frame: str) -> tuple[str, int, bool]

Invert encrypt.

Returns (message, timestamp, scenario). Mainly useful for tests and for debugging captured frames.