Desktop temperature logger for the Intrepid neoRAD-IO2-TC thermocouple module (8 isolated K-type banks). Native C++20, SDL3 + Dear ImGui, talks to the module through Intrepid's libneoradio2 over USB HID. Windows, Linux and macOS.
This is stage 1 of two: a cross-platform GUI. Stage 2 is the same logger running on a Free-WiLi 2; see "Stage 2" below.
- Finds attached RAD-IO2 chains (plus a built-in Simulator entry so the app can be tried with no hardware).
- Connect → identifies the chain, starts application firmware on every bank, shows per-bank type, serial and firmware version.
- Polls all banks at a configurable interval (0.1 s – 60 s), shows the thermocouple temperature and, when the firmware reports it, the cold junction temperature, in °C or °F.
- Strip chart of the last N seconds (up to 4 h kept in memory) with per-bank toggles, auto-scale, hover readout.
- CSV logging: one row per poll, all banks, wall-clock timestamp + elapsed
seconds. Rows are flushed as written, so pulling the cable never loses more
than one row. Files default to
logs/beside the executable.
CSV columns: timestamp,elapsed_s,bank1_tc_C..bank8_tc_C,bank1_cj_C..bank8_cj_C
(unit suffix follows the unit chosen when logging was started; banks without a
module leave the cell empty).
Needs CMake ≥ 3.24, Ninja, a C++20 compiler, git (dependencies are fetched).
Linux additionally needs libudev-dev and the usual X11/Wayland dev headers
for SDL.
git clone --recursive https://github.com/freewili/radio2tc.git
(the freewili/wilibsp submodule is only needed for stage 2; the desktop
build works without it).
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build # runs the console test binaries
The finished, portable application is the folder build/stage/:
stage/
radio2tc(.exe)
assets/99-intrepidcs.rules
README.md
logs/ created on first run
Copy that folder anywhere and run it. SDL3, ImGui, libneoradio2, hidapi and
the C++ runtime are linked statically (MSVC: /MT); no redistributable is
needed. All runtime paths are resolved from the executable's own directory.
/dev/hidraw* is root-only by default. Install the udev rule once:
sudo cp assets/99-intrepidcs.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
then unplug/replug the module. Until this is done the device shows up in the list but Connect fails with a permission error.
src/main.cpp SDL/ImGui bootstrap and the frame loop
src/app/ state, ring buffer, CSV logger, poller thread,
simulator — no ImGui, no OS calls
src/platform/ libneoradio2 backend, executable-relative paths
src/ui/ all drawing (reads a Snapshot, never owns state)
tests/ console tests (ring buffer, CSV, poller)
assets/ udev rule, staged beside the binary
The device is driven on a worker thread (app::Poller); the UI copies a
Snapshot each frame, so a slow USB read can never stall rendering.
Failures are bool + error string, never exceptions.
Sequence per libneoradio2: neoradio2_find → open → chain_identify →
app_start(0, 0xFF) (modules boot into the bootloader) → per bank
get_device_type/serial/firmware. Each poll is one
request_sensor_data(0, 0xFF, ENABLED) followed by
read_sensor_array per present bank; the first 4 bytes are the calibrated
temperature as a little-endian float in °C, and a second float, when present,
is the cold-junction temperature. The firmware tested (module IA0372) returns
only the 4-byte value and reports an unconnected thermocouple as -1000 °C;
the app shows that as "open (no probe)" and leaves the CSV cell empty.
freewili/ holds the FreeWili 2 port — freewili/README.md explains how it
works, with screenshots.
The RAD-IO2-TC plugs straight into the FreeWili 2 USB-A port: its FT260
HID-to-UART bridge is driven by the display CPU through the RP2350's native
USB host, using the same feature-report setup and 250 kbaud framing that
libneoradio2 uses on the PC. Readings go to the LCD — bank list or a live strip chart, FW2
soft-menu bar labelling the physical buttons (graph/rate/log/unit/clock) and to timestamped /appdata/radio2tc/*.csv files
on the SD card; freewili/tools/set_clock.py seeds the clock from the PC.
Shared between both stages: src/radio2/protocol.* — the RAD-IO2 frame
encoder/parser, CRC-8 and FT260 report packing, unit-tested by
tests/test_protocol.cpp on the host.
cd freewili && ./build.sh # -> build/radio2tc.uf2
python wilibsp/tools/fw.py install-app build/radio2tc.uf2 # copy to SD:/apps
Status: verified on a FreeWili 2 with the real module on 2026-08-25
(enumeration, 8-bank identify, polling with live readings from two probes,
screen, buttons, SD logging and read-back); see freewili/docs/findings/.
MIT — see LICENSE. Vendored third-party components (SEGGER RTT, FatFs) keep their own permissive licenses, listed in THIRD-PARTY-NOTICES.md and in the vendored file headers. Drivers reused from other FreeWili 2 repositories carry the MIT/BSD-3-Clause terms noted in their file headers.

