feat(firmware): add experimental RTL-SDR USB-host module (Workstream J)
Self-contained ESP-IDF component (firmware/esp32p4-sensor-node/components/ rtlsdr_experimental/) exploring RTL2832U-over-USB-host on the ESP32-P4, ported from frontend/src/lib/sdr.ts's researched WebUSB protocol sequence (vendor commands, I2C-repeater tuner init) to the ESP-IDF USB Host Library. Implements: USB Host Library install/client lifecycle, RTL2832U/Terratec vendor-ID device matching, the demod+R820T init vendor-command sequence over control transfers, a pipelined bulk-IN read loop for raw IQ, and an inert-by-default upstream IQ-forwarding stub targeting a proposed separate binary endpoint (not the JSON telemetry shape — reasoning documented in the README) since no such backend endpoint exists yet. Off by default (RTLSDR_EXP_ENABLE Kconfig, default n). Unverified against real hardware and never compiled (no ESP-IDF toolchain in this environment) — marked as such in every source file and in a dedicated "Workstream J" section of firmware/esp32p4-sensor-node/README.md, which this commit also creates since Workstream I's core skeleton (owned by a separate, unmerged worktree) hadn't created one yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* rtlsdr_experimental.h — EXPERIMENTAL RTL2832U-over-USB-Host module for the
|
||||
* ESP32-P4 sensor node (Workstream J, ESP32-P4 Sensor Node spec).
|
||||
*
|
||||
* ============================================================================
|
||||
* HARDWARE PASS REQUIRED — UNVERIFIED AGAINST REAL HARDWARE
|
||||
* ============================================================================
|
||||
* This entire module (this header + rtlsdr_experimental.c) has been written
|
||||
* from documented ESP-IDF USB Host Library API shape and the RTL2832U/R820T
|
||||
* register sequence already researched for this project in
|
||||
* frontend/src/lib/sdr.ts (a WebUSB driver, real and documented against
|
||||
* librtlsdr's public register map). It has NOT been compiled against a real
|
||||
* ESP-IDF toolchain (none is available in this environment) and has NOT run
|
||||
* against real ESP32-P4 + RTL2832U dongle hardware. Treat every register
|
||||
* value, timing constant, buffer size, and API call signature here as
|
||||
* "structurally reasoned, not verified." See the "Workstream J" section of
|
||||
* ../../README.md for the full honesty write-up, including exactly what a
|
||||
* real hardware bring-up session needs to check before this is trusted.
|
||||
*
|
||||
* Scope reminder: this is a self-contained ESP-IDF *component*, deliberately
|
||||
* kept out of `main/` so it can be dropped into Workstream I's firmware
|
||||
* project skeleton (WiFi/HTTP/BME280/presence-sensor core) without touching
|
||||
* any file that workstream owns. Integration is one line in the consuming
|
||||
* project: add this directory to EXTRA_COMPONENT_DIRS (or copy it under that
|
||||
* project's own components/) and call rtlsdr_exp_init()/rtlsdr_exp_start()
|
||||
* from wherever app_main() sets up its other sensor drivers.
|
||||
* ============================================================================
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* USB identification — ported 1:1 from frontend/src/lib/sdr.ts
|
||||
* (RTL2832U_VENDOR / RTL2832U_PRODUCTS / TERRATEC_VENDOR, lines ~30-34).
|
||||
*
|
||||
* sdr.ts deliberately filters WebUSB's device picker by *vendor* id only
|
||||
* (see its requestDevice() comment: "many dongles report product ids
|
||||
* outside the handful we know, so filtering by productId hides them from
|
||||
* the picker"). We mirror that: RTLSDR_EXP_KNOWN_PRODUCT_IDS below is
|
||||
* informational only (used for a startup log line), never a hard match
|
||||
* filter. Any device presenting vendor id RTL2832U or TERRATEC is treated
|
||||
* as a candidate and bring-up is attempted.
|
||||
* ------------------------------------------------------------------------ */
|
||||
#define RTLSDR_EXP_VENDOR_RTL2832U 0x0BDAu
|
||||
#define RTLSDR_EXP_VENDOR_TERRATEC 0x0CCDu
|
||||
|
||||
/* Known RTL2832U product ids (informational/logging only — see above). */
|
||||
extern const uint16_t RTLSDR_EXP_KNOWN_PRODUCT_IDS[4];
|
||||
#define RTLSDR_EXP_NUM_KNOWN_PRODUCT_IDS 4
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Configuration
|
||||
* ------------------------------------------------------------------------ */
|
||||
typedef struct {
|
||||
/* Tuning defaults, applied once bring-up completes. Matches sdr.ts's
|
||||
* open(sampleRateHz = 2_048_000) default and its FM-band test tune of
|
||||
* 98 MHz — pick whatever the product actually wants to listen to. */
|
||||
uint32_t center_freq_hz; /* e.g. 98_000_000 */
|
||||
uint32_t sample_rate_hz; /* e.g. 2_048_000 */
|
||||
|
||||
/* Bulk-IN read tuning. bulk_read_chunk_bytes MUST be a multiple of 512
|
||||
* (USB high-speed bulk max packet size) — this mirrors sdr.ts's own
|
||||
* assumption ("Length must be multiple of 512" on readSamples()).
|
||||
* UNVERIFIED: real chunk size vs. USB Host Library heap/DMA limits and
|
||||
* actual achievable throughput needs a real device. See README. */
|
||||
size_t bulk_read_chunk_bytes; /* default suggestion: 16384 */
|
||||
size_t bulk_read_queue_depth; /* in-flight pipelined transfers, e.g. 4 */
|
||||
|
||||
/* Upstream IQ forwarding (see rtlsdr_exp_forward_iq_block() and the
|
||||
* "IQ forwarding architecture" section of the README for the reasoning
|
||||
* behind a *separate* raw-binary stream rather than reusing the JSON
|
||||
* sensor telemetry endpoint). NOTE: the backend endpoint referenced
|
||||
* here does not exist yet anywhere in this repo as of this workstream —
|
||||
* this is a client-side stub against a *proposed* shape, not a wired
|
||||
* integration. */
|
||||
const char *iq_upload_url; /* e.g. "https://host/api/device/iq-stream" */
|
||||
const char *device_bearer_token; /* same raw pairing token used for
|
||||
* the regular telemetry POST loop */
|
||||
} rtlsdr_exp_config_t;
|
||||
|
||||
/* Fills in documented, conservative defaults. Caller still MUST set
|
||||
* iq_upload_url / device_bearer_token before starting. */
|
||||
void rtlsdr_exp_config_default(rtlsdr_exp_config_t *config);
|
||||
|
||||
typedef struct rtlsdr_exp_handle_s *rtlsdr_exp_handle_t;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Lifecycle
|
||||
* ------------------------------------------------------------------------ */
|
||||
|
||||
/* Allocates internal state. Does NOT touch USB hardware yet. */
|
||||
esp_err_t rtlsdr_exp_init(const rtlsdr_exp_config_t *config, rtlsdr_exp_handle_t *out_handle);
|
||||
esp_err_t rtlsdr_exp_deinit(rtlsdr_exp_handle_t handle);
|
||||
|
||||
/* Installs the USB Host Library, registers a client, and spawns the
|
||||
* background tasks that watch for a matching RTL2832U device, run the
|
||||
* init vendor-command sequence on attach, and (once bring-up succeeds)
|
||||
* start the bulk-IN read/forward loop. Non-blocking — returns once the
|
||||
* tasks are created, not once a device is found.
|
||||
*
|
||||
* UNVERIFIED beyond "this is the documented shape of the ESP-IDF USB Host
|
||||
* Library API" — see README for exactly what needs a real board to check. */
|
||||
esp_err_t rtlsdr_exp_start(rtlsdr_exp_handle_t handle);
|
||||
|
||||
/* Stops streaming, releases the USB interface/device if held, deregisters
|
||||
* the USB Host client, and tears down the background tasks. Does NOT call
|
||||
* usb_host_uninstall() if other USB Host clients might still be active in
|
||||
* the wider firmware project (Workstream I owns whether anything else in
|
||||
* the project also uses USB host) — see README integration notes. */
|
||||
esp_err_t rtlsdr_exp_stop(rtlsdr_exp_handle_t handle);
|
||||
|
||||
/* Best-effort retune while streaming — same caveats as sdr.ts's
|
||||
* setFrequency()/setSampleRate() (simplified PLL/resampler math, marked
|
||||
* HARDWARE PASS REQUIRED there too). */
|
||||
esp_err_t rtlsdr_exp_set_frequency(rtlsdr_exp_handle_t handle, uint32_t hz);
|
||||
esp_err_t rtlsdr_exp_set_sample_rate(rtlsdr_exp_handle_t handle, uint32_t hz);
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Observability — deliberately exposed so this experimental module can
|
||||
* report its own health (e.g. folded into the regular sensor_type registry
|
||||
* as a synthetic "sdr_status" reading) rather than fail silently.
|
||||
* ------------------------------------------------------------------------ */
|
||||
typedef struct {
|
||||
bool device_present; /* a matching-vendor USB device is enumerated */
|
||||
bool bring_up_ok; /* init vendor-command sequence completed without
|
||||
* a control-transfer error (does NOT mean the
|
||||
* tuner/demod are actually producing valid IQ —
|
||||
* only real hardware + a spectrum check can
|
||||
* confirm that) */
|
||||
bool streaming;
|
||||
uint32_t bulk_reads_ok;
|
||||
uint32_t bulk_reads_failed;
|
||||
uint32_t iq_blocks_dropped; /* forward queue was full — see README */
|
||||
uint32_t bytes_forwarded_upstream;
|
||||
uint32_t forward_failures;
|
||||
} rtlsdr_exp_stats_t;
|
||||
|
||||
void rtlsdr_exp_get_stats(rtlsdr_exp_handle_t handle, rtlsdr_exp_stats_t *out_stats);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user