Skip to content
Development
Skill

/sdr-receiver

Drive the Embedded AI Harness workbench's RTL-SDR receiver over `/api/sdr/*` — decode/analyze/power captures, the phased `acquire`, the interactive live rtl_433 console, "AI Sherlock" record→reverse-engineer, USB recovery, and the rtl_433 device database. Use this whenever the

From plugin
embedded-ai-harness
16118 skills
Install
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill sdr-receiver --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/sdr-receiver

Context preview

The summary Claude sees to decide when to auto-load this skill.

Drive the Embedded AI Harness workbench's RTL-SDR receiver over `/api/sdr/*` — decode/analyze/power captures, the phased `acquire`, the interactive live rtl_433 console, "AI Sherlock" record→reverse-engineer, USB recovery, and the rtl_433 device database. Use this whenever the

SKILL.md

sdr-receiver.SKILL.md
name: sdr-receiver
description: Drive the Embedded AI Harness testbench's RTL-SDR receiver over `/api/sdr/*` — decode/analyze/power captures, the phased `acquire`, the interactive live rtl_433 console, "AI Sherlock" record→reverse-engineer, USB recovery, and the rtl_433 device database. Use this whenever the user wants to receive, sniff, decode, analyze, or reverse-engineer an RF signal (433/315/868 MHz remotes, sensors, TPMS), read RSSI/pulse timing, recover a wedged dongle, or add a device to rtl_433 — even if they only say "rtl_433", "rtl-sdr", "sniff a remote", "what frequency is this", "signal too strong", "no signal", "AI Sherlock", or "add this remote". This is the receive-side counterpart to the transmit-only `signal-generator` skill. Always GET `/api/sdr/status` first to confirm the dongle is detected.

SDR Receiver (`/api/sdr/*`)

The testbench Pi has one RTL2832U dongle behind the `rtl_433` toolchain. Every receive operation goes through `/api/sdr/*`. It is the receive-side counterpart to the transmit-only `signal-generator` skill — never SSH in to run `rtl_433` yourself, drive the API.

**One dongle, one user.** Every capture (and the whole live console) holds a single-instance lock. While one is running, the others return `"SDR busy — a capture is already running"`. Stop the live console before a one-shot, and vice-versa.

---

Always check status first

**GET `/api/sdr/status`** before anything:

{"ok": true, "active": false, "mode": null, "freq_hz": 0,
 "hardware": {"rtl_433": true, "rtl_test": true, "device": true},
 "available": true}
  • `hardware.device: false` / `available: false` → the dongle isn't detected. Try

`POST /api/sdr/reset` (USB reset); if still absent, it's unplugged or the USB controller is wedged (see **Dongle recovery**).

  • `active: true` → something is already using the dongle; stop it first.

---

API summary

Every endpoint, with its request and response shape: [FSD Appendix D.10](../../../docs/Harness-FSD.md#d10-sdr-receiver).

Only the choice between them is this skill's: `capture` decodes, `analyze` gives raw pulse timing when nothing decodes, `power` measures a level, and `acquire` runs all three in sequence when you do not yet know what is on the air.

Common body fields: `freq_hz` (default 433.92 MHz), `duration_s`, `gain` (number dB, or omit for AGC), `sample_rate` (default 250 kHz — keep low, it's a Pi Zero 2 W), `flex` (an `-X` spec).

**A dongle plugged in after boot is picked up automatically** — status re-probes while the SDR is idle, so there is no need to restart the portal. If `device` stays `false`, the dongle really is absent or not enumerating.

**Pin `gain` on anything you will compare.** On AGC the tuner rescales from whatever it saw recently, so the same quiet band reads tens of dB apart between calls and a strong carrier compresses instead of standing clear. This bites hardest on `power`, where the whole point is comparing numbers.

**Is the dongle working at all?** `pytest pytest/ -k wt1909` transmits on 86.784 MHz (5th harmonic = 433.92 MHz) and requires the dongle to see a ≥15 dB lift, then a drop. It needs no DUT and no operator. Run it before debugging a "nothing decodes" complaint — it separates a dead receive path from a signal problem in one step.

---

The two big signal traps (learned the hard way)

1. **AGC saturates a too-close source.** With the tuner's default auto-gain, a strong near-field transmitter rails the input to full scale and fills an OOK signal's off-gaps — so a crisp remote reads as a continuous / misdetected **FSK** carrier and slices to all-zero codewords. Fix: **more distance** or a **fixed `gain`** (e.g. 33–40 dB). The tell is RSSI pegged near 0 dB regardless of distance, and `rtl_433` reporting FSK for a known-OOK remote.

2. **Decode mode is empty for unknown remotes.** Plain `decode` only emits for a *known* protocol. An unrecognised remote produces nothing — and no RSSI. Use `analyze` (or the live console, which runs `-A` in every mode) to see the signal regardless of decode. **Signal presence ≠ decodability.**

---

Live console (`/api/sdr/live/*`)

A persistent `rtl_433` whose merged output a reader thread fans into a sequence-numbered ring buffer the browser fast-polls (~500 ms; nothing dropped). `-A` runs in **every** mode, so the RSSI meter shows every burst's strength even when it doesn't decode.

`POST /api/sdr/live/start` body:

{"freqs": [433920000], "mode": "decode",
 "gain": 33.8, "sample_rate": 250000, "squelch": false,
 "hop_interval": 5, "flex": "n=r,m=OOK_PWM,s=340,l=2068,r=13936", "isolate": false}
  • `freqs`: one = locked, several = **hop** (`-H hop_interval`). Bands: 433.92 /

315 / 868. Hop to *find*, then lock to a single freq to *work* (hopping listens to each band ~1/3 of the time and misses momentary presses).

  • `mode`: `decode` (all built-ins) · `flex` (`-X`, `isolate:true` adds `-R 0`) ·

`analyze` (`-A` only).

  • `gain`: omit for AGC; a number sets a fixed tuner gain.
  • Poll `GET /api/sdr/live?since=<seq>`; each entry is `{seq, line, event}` where

`event` is the parsed JSON (or a synthetic `{rssi,snr,noise,analyzer:true}` from an analyzer `RSSI:` line). RSSI is **burst-driven** — it updates on a press, not continuously.

---

AI Sherlock — reverse-engineer an unknown remote

The record→analyze flow the console exposes as one toggle button:

1. **Start** (`/api/sdr/log/start`) — record the live stream (analyze+AGC). 2. Operator presses each key a few times. 3. **Stop** (`/api/sdr/log/stop`) — freeze the log. 4. `GET /api/sdr/log` → an assistant reads the bursts and derives: modulation/timing, the **constant preamble/device-ID**, and the **per-key varying field**, plus a decoder spec and button→code map.

`start_log` **clears** the previous recording (single in-memory buffer, lost on restart) — it's a record-analyze-now loop, not persistent storage.

---

Device database (make a

Read more
Ships withembedded-ai-harness

Spec to silicon, hands off.

Get the whole plugin
Stats
166
Stars
53
Forks
Active
Maintenance
Python
Language
MIT
License
7d ago
Last commit
6mo ago
Created

Repo: SensorsIot/Embedded-AI-Harness

Other skills on embedded-ai-harness.