The agent capture loop calls sdr.rx(buffer_size) per chunk. USRP inherited the
base rx() → record(), which issued start_cont/stop_cont and slept 0.1s EVERY
buffer. At 2.5 MSps that captured ~1.6 ms of IQ per ~100 ms — heavily gapped,
transient-laden, and zero-filled on timeout, which rendered as choppy/black
bands in the spectrogram.
USRP.rx() now keeps a single continuous stream running across calls:
- issues start_cont once (lazily, on first rx()),
- recv()s until the request is filled, carrying any over-read into a residual
buffer so nothing is dropped between rx() boundaries (gapless),
- tolerates overflow (samples still valid), treats repeated timeouts as a
disconnect, and stops the stream on close().
Hardware-free tests stub uhd and prove gaplessness, single start, overflow
handling, timeout->disconnect, and stop cleanup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heartbeat detect_devices() shelled out to uhd_find_devices every 60s. When a
USB SDR (USRP B2x0) was mid-capture, that probe disrupted the live stream and
the device briefly vanished ("No UHD Devices Found"), killing the capture.
detect_devices() gains a probe flag; heartbeat_payload passes probe=False
whenever a session is active, returning the last good enumeration (or a
driver-only list) instead of touching the hardware.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make `ria-agent stream` work with any SDR the agent has drivers for, with
no per-device config in the hub:
- heartbeat advertises rich `hardware` entries {device, identifier, label,
connected} via hardware.detect_devices(); USRP is enumerated into concrete
instances (uhd_find_devices), others advertise driver-only entries. The
identifier is chosen to round-trip through parse_ident (None=auto-select or
name=...), so a device address is never a bare value.
- ship udev rules (Pluto/RTL-SDR/HackRF/USRP B2x0/bladeRF) + `ria-agent
install-udev` so USB radios open without sudo — one privileged step, all
inside the toolkit.
- streamer surfaces a "run: sudo ria-agent install-udev" hint on USB
permission errors instead of the cryptic UHD message.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Most users register against the production hub, so requiring --hub on
every invocation was friction. Default the flag to https://riahub.ai
via a module-level DEFAULT_HUB_URL constant; explicit --hub still wins,
so dev and self-hosted setups (e.g. http://whitehorse:3005) keep working.
The legacy `ria-agent run` path keeps its own --hub handling unchanged
— it has a config-file fallback and existing operators rely on it.
Bumps version to 0.1.8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Restore agent_id in success output. Pre-PR the user saw the hub's
canonical identifier; the merge had reduced this to just `(name)`,
which made it impossible to correlate the registered agent with
anything on the hub side without inspecting the config file.
- Add a 15s timeout to the register POST. urllib's default is none,
so a stuck hub would block the CLI indefinitely.
- Read User-Agent version from package metadata instead of hardcoding
"0.1", so it tracks releases. Also corrected the URL to the canonical
Source URL listed in pyproject.toml (was pointing at a likely-404
github.com path).
- Add two tests guarding the User-Agent. The whole point of the Cloudflare
fix was to set a non-default UA; previously no test asserted this, so
a refactor could silently reintroduce the 403 code 1010 bug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Map the hub's structured `{detail: {reason}}` responses (invalid_key,
expired, revoked, already_consumed) and 429 rate-limits to actionable
CLI messages so users know to mint a fresh key from Settings → RIA
Agents instead of seeing a raw HTTPError.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>