Navya Sharma // personal site
← projects

Hardware · 2026

radeye-b20 — a Python driver for an undocumented survey meter

A pip-installable driver for the Thermo Scientific RadEye B20 radiation survey meter, reverse-engineered from the wire and published on PyPI under MIT.

PythonpyserialPyPIGitHub ActionsReverse engineering

The Thermo Scientific RadEye B20 is a handheld radiation survey meter. It streams live readings out of its infrared read-head, but the serial protocol is undocumented and — as far as I can tell — no public library speaks it. This one does.

It was reverse-engineered by watching the wire and checking decoded values against the meter's own display, then packaged as a small, dependency-light Python library so anyone with the same instrument can log and plot from it without repeating the work.

pip install radeye-b20
from radeye_b20 import RadEye

with RadEye() as meter:            # auto-detects the IR-USB cable
    for reading in meter.stream():
        print(reading.value, reading.unit)   # e.g. 3.51 cps

There's a command line too, for when you just want a CSV:

radeye ports                 # list serial ports, flag the likely RadEye
radeye stream                # live readings until Ctrl+C
radeye stream --csv          # timestamp,value,unit  (pipe to a file)

How the protocol works

The meter sends fixed ASCII frames at 9600 baud, 7 data bits, even parity, 1 stop bit, each framed by STX … ETX CR LF. A decoded frame looks like this:

351 5  0 0 04 FH41B2  0 42
 |   |            |       └─ checksum (algorithm unknown — exposed, not verified)
 |   |            └───────── device / probe tag
 |   └────────────────────── unit code: 5 = cps, 3 = cpm
 └────────────────────────── value digits (decimal point implied by the unit)

There's no decimal point on the wire, so the integer is scaled per unit (cps × 0.01, cpm × 0.1). The cable is a Prolific PL2303, which the driver auto-detects by USB vendor ID, and whose occasional zero-length-read glitches it tolerates without dropping the connection.

Each reading carries the scaled value, the unit, a timestamp taken at decode time, the raw digits, the unit code, the trailing checksum token, and the cleaned raw frame — so nothing the meter said is hidden from you.

Honest limits

I'd rather you know these up front than discover them:

  • Reverse-engineered, not from a spec. Verified against one meter's display.
  • Only cps and cpm are trusted. Other unit and range modes are surfaced as code:<n> and passed through unscaled — the meter has modes I couldn't test, and I won't pretend the scaling for those is right.
  • The checksum is not verified. The algorithm is unknown; the field is exposed so you can experiment, but frames aren't validated against it.
  • Scaling may be meter-dependent. If readings are off by a constant factor, check against the display and set the scale factors accordingly.

Not for safety-critical use

This is a research and logging convenience. It is not calibrated, validated, or certified, and must not be relied on for radiation-safety decisions, dose assessment, or any situation where a wrong or missing reading could harm someone. For that, use the instrument's own display and your organisation's approved procedures.

Published

On PyPI as radeye-b20, source on GitHub, MIT licensed. Releases go out through PyPI Trusted Publishing from a GitHub Actions workflow, so no API token is stored anywhere. If you have a B20 and can capture frames in a mode this doesn't handle — dose-rate especially — raw frames alongside the displayed value are exactly what's needed to extend it.