Skip to content

Web/JS recipes

Sketches adapted from the open web client (src/lib/naya.ts): Web Serial / Web BLE transport, same wire bytes as Python.

Frame build + parse (TypeScript sketch)

function buildFrame(dst: number, type: number, c0: number, c1: number, params: number[]): Uint8Array {
  const body = [type, params.length + 2, c0, c1, ...params];
  let crc = 0;
  for (const b of body.slice(1)) crc ^= b;   // XOR from C0
  return new Uint8Array([0xaa, 0x00, dst, 0x00, ...body, crc, 0x04]);
}

interface Frame { src: number; type: number; c0: number; c1: number; payload: Uint8Array }

function parseFrame(f: Uint8Array): Frame {
  let crc = 0;
  for (let i = 6; i < f.length - 2; i++) crc ^= f[i];
  if (crc !== f[f.length - 2]) throw new Error("CRC mismatch");
  return { src: f[1], type: f[4], c0: f[6], c1: f[7], payload: f.slice(8, -2) };
}

// Layer-echo ACK: accept 00 00 OR 00 <layer>
function isWriteAck(ack: Uint8Array, layer: number): boolean {
  return ack.length === 2 && ack[0] === 0x00 && (ack[1] === 0x00 || ack[1] === layer);
}

Session calls (client API shape)

await ses.cmd(0xfa, 0x10, 0x01, []);        // fa/1001 device info
await ses.cmd(0xfe, 0x10, 0x02, []);        // fe/1002 firmware version
await ses.writeKey(record, layer);           // 30/1004 + layer-aware ACK check inside
await ses.writeLed(kk, h, s, layer);        // 30/100e single-entry
await ses.readModuleConfig(layer);          // 30/100b multipart
const t = await ses.getTimeouts();           // fe/100b → { idleMs, sleepMs, deepMs }
await ses.setTimeouts(idleMs, sleepMs, deepMs); // fe/100a 13 B payload

Notes: cmd() returns the parsed frame; settings ACKs carry no layer echo (status byte 0x00 check, not isWriteAck); never send fe/100a as a “commit”; ED payloads are always [target, value…]; right-half 30/10xx and ed/1014 never answer — don't await them, fail fast.