objectThe Onyx Geiger counter is equipped with a USB interface powered by an FTDI chip. This USB interface serves two main purposes: Upgrade device firmware, and generic serial interface.

Device firmware upgrade

The design of the upgrade interface is very smart: whenever the USB port is connected, this triggers a reset of the CPU of the Onyx. This means that even if a really buggy or invalid firmware is loaded, it is easy to get out of a crashed device. And the FTDI chip, beyond its serial I/O capabilities, can also control GPIO lines: this is used by the firmware downloader to force the CPU in Device Firmware Update (DFU) mode independently from the device firmware itself. That way, a totally crashed device can always be reflashed with no need for anything but a simple USB cable. Nice, eh ?

Serial protocol

The first released revision of the Onyx firmware only supported very basic text commands which returned human-readable values with no easy way to parse the results using software. To be honest, it was not very usable beyond simple debugging.

For this reason I decided to implement a proper serial protocol for the device, since I was also developing a software client.

JSON-based protocol

The initial version of the firmware already output the contents of the log as a large JSON structure. I have used that approach for a lot of other embedded projects too, so it was only logical to push this further and implement a complete set of JSON commands and return values. This makes it both easy to read what is going on for debugging, and is super simple to parse using software.

In the past, most embedded software implemented I/O with very compact binary protocols in order to save flash and ram space and improve speed and reliability. With today’s micro controllers, this approach is becoming less and less relevant: lots of RAM, lots of flash, and serial over USB provides speed and a very high level or error correction.

The basics of the protocol are as follow:

Get commands

Get commands use a simple { "get": "value" } format.

Important note: only issue one get value per command, multiple gets such as {"get":"rtc","get":"guid"} are NOT supported at this time.

The commands that are implemented are:

get command Role Response
cpm Get the current CPM reading { "cpm": {
"value": val, // Actual value
"valid": boolean, // Valid flag
"raw": val, // Uncompensated value
"cpm30": val // 30 second window
guid Get device unique ID { "guid": String }
rtc Get device Real time clock (Unix timestamp, integer) { "rtc": Number }
devicetag Get the device asset tag. As opposed to the GUID, this asset tag can be updated freely. Is no device tag is set, returns "No device tag set". { "devicetag": String }
settings Return all settings key/value pairs { "keys" : {
"key1": "value1",
"key2": "value2",
version Get firmware version. { "version": String }
logstatus Returns the current log status: logging period in seconds, how many slots are used, how much are available in total {"logstatus": {
"used": Number,
"total": Number,
"interval": Number
} }

Set commands

Set commands are used to update device settings. Only a few are implemented at this stage, but it is very straightforward to add more if necessary.

If the set command is successful, the device responds with a {"ok": "command_name"}. Otherwise it issues an {"error": "command_name"}

Command Role Format
devicetag Set asset tag { "set": { "devicetag": String } }
rtc Set device clock. the argument is the time in UTC as a Unix timestamp (integer). { "set": { "rtc": Number } }

Leave a Reply

Your email address will not be published. Required fields are marked *