Hire Me

ELMterm, CornucopiaStreams, and the joy of clean automotive telemetry

Mickey · · 4 min read

Back in 2016 I fell deep into the rabbit hole of automotive diagnostics (long-time readers may remember OBD2 Expert). What started as “let me decode one CAN frame” quickly became a multi-year tour of every adapter I could get my hands on: ELM327 clones, genuine STN units, Bluetooth dongles, USB-serial cables, Wi-Fi gateways, even a few early BLE UART experiments. Every single one of them promised to be the answer, and every single one of them let me down in its own special way: flaky transports, unreadable hex dumps, and notebooks full of copy-pasted traces. *sigh* Eventually I built my own hardware so I could at least trust the bits on the wire, but I still lacked a terminal that respected the data I was seeing. Anyway, that’s why ELMterm exists.

Why another terminal?

Classic tooling (nc, telnet, minicom, picocom, …) treats adapters like anonymous byte pipes. That’s fine for a quick sanity check, but terrible when you’re juggling OBD-II, UDS, ISO-TP, and vendor quirks day in, day out. ELMterm knows the automotive domain: it annotates AT/ST commands, explains diagnostic modes, reassembles ISO-TP bursts, and highlights negative responses, ASCII payloads, and VIN frames automatically. It’s my way of keeping the conversation with the car readable, whether I’m validating my own adapter firmware or reverse-engineering a random aftermarket device.

Powered by CornucopiaStreams

The other half of the story is CornucopiaStreams, my transport abstraction that grew out of those same diagnostic sessions. After testing dozens of adapters I wanted one code path for every physical link. CornucopiaStreams is that “horn of plenty” (yes, I couldn’t resist the latin name): point it at a tty://, tcp://, ble://, ea://, or rfcomm:// URL and it hands back a matched Input/OutputStream pair. Serial over USB, Wi-Fi bridges, BLE UARTs, even MFi External Accessories all look identical to ELMterm. That means I can swap transport layers in seconds — one moment I’m on a USB cable in the lab, the next I’m walking around the car with a BLE prototype — without changing a line of REPL logic.

Fan-in diagram: five adapter URLs (tty, tcp, ble, ea, rfcomm) converge into CornucopiaStreams, which hands back a single InputStream/OutputStream pair; ELMterm's REPL talks to that pair only, so swapping the physical link does not change a line of REPL logic.
The REPL stays the same while the physical transport changes underneath it.

Where to get ELMterm

ELMterm lives on GitHub under the Automotive-Swift org:

Clone it and build the release target:

git clone https://github.com/Automotive-Swift/ELMterm.git
cd ELMterm
swift build -c release

The binary will appear at .build/release/ELMterm. Because it’s a SwiftPM project you can also swift run ELMterm … while iterating.

How I use it day to day

Pick any adapter URL that CornucopiaStreams understands and pass it to ELMterm:

## USB serial, common bitrate
tty_url="tty://adapter:115200/dev/cu.usbserial-ELM"
ELMterm "$tty_url"

## Wi-Fi bridge
tcp_url="tcp://192.168.0.10:35000"
ELMterm "$tcp_url"

## BLE prototype (service FFF0)
ble_url="ble://FFF0"
ELMterm "$ble_url"

Once connected you get a readline-style prompt (configurable via --prompt) with command history that persists to ~/.elmterm.history by default. I tend to keep timestamps on (--timestamps) when validating long bench sessions, and the new theming support lets me switch between a light palette (perfect for my yellow terminal) and a dark palette for late-night hacking (--theme light|dark).

A few other flags I reach for:

Inside the REPL there are helper commands (:history, :clear, :analyzer on|off, :save, :quit) so I rarely leave the keyboard while probing.

What’s next?

ELMterm and CornucopiaStreams continue to evolve together. Every time I touch a new transport — say, BLE L2CAP with a custom ECU, or a TCP bridge baked into a logger — I add the capability to CornucopiaStreams and immediately reap the benefit in ELMterm. Likewise, improvements in the analyzer (better ISO-TP handling, rich metadata, smarter VIN extraction) go straight into my daily workflow. Building my own adapter took away the “can I trust the hardware?” question; ELMterm takes away the “can I trust my tooling?” question. What’s left is “can I trust the ECU?” — and believe me, you can’t ;)

If you work on OBD-II/UDS diagnostics, or you simply want a terminal that actually understands what your automotive adapter is telling you, give ELMterm a try. Clone it, point it at your favorite transport, and let me know what’s missing — I’m sure there’s plenty.

Cheers, :M: