Selected work

03 Case study Private

Augur

Bring up a Devantech dS3484 Ethernet relay module with an Xsens GNSS/INS sensor. A headless build-and-flash tool, an NMEA decoder that runs on the module itself, and a declared data feed, so the board knows its own position, heading and tilt and switches a relay from a geofence with no computer in the chain.

Role
Solo
Timeline
Jul – Aug 2026
Stack
dScript (on-module) · Java · Python · vanilla JS
Status
Working end to end on the bench
An unbranded aluminium inertial sensor and a small embedded controller board joined by a ribbon cable, beside a GNSS patch antenna, one cobalt LED lit.

01 Problem

The Devantech dS3484 is an Ethernet relay and I/O module that runs small programs written in the vendor's own language, dScript, built and flashed through a desktop IDE. The Xsens MTi-680G is a GNSS/INS sensor that reports position, heading and tilt as NMEA sentences over RS232.

The goal was to make the board decide for itself: decode the sensor on the module, compute where it is, switch a relay from a geofence with no computer or gateway in the chain, and hand the data back out cleanly. The tooling also had to be something another engineer could run from their own laptop.

The project is named for the Roman augur, who marked out a region of the sky, declared in advance what he was watching for, and reported only what appeared inside it. That became the working rule: declare the region first, report what was observed, claim nothing further.

02 What I built

  • A headless flasher. It calls the vendor's own compiler and bootloader classes directly, writing exactly the bytes the IDE would, with a power pre-flight check and clear exit codes.
  • An NMEA decoder that runs on the module. It hunts across six baud rates and three serial headers, and only declares a lock on a sentence whose checksum verifies. All of it is fixed-point, because dScript has no floats.
  • A declared data feed. An XML endpoint with a written contract, audited against the firmware in both directions: declared but not delivered, and computed but not declared.
  • A local control panel. A ten-step self-test that reports each link of the chain separately, a satellite view drawn on a 2D canvas so it works offline, a QR code to open the module on a phone, and a CSV logger.
4Sentence types decoded
97Feed fields
10Self-test steps
0.1 sPort check (vs ~45 s scan)

03 Architecture

Three paths. Build and flash runs from the laptop over USB. The sensor feeds the module over RS232, where the decoder and geofence run. The data comes back out over Ethernet to the panel, a phone, or a logger.

The laptop control panel builds firmware with the vendor compiler and flashes it over USB to the dS3484 module. The Xsens sensor sends NMEA over RS232 to a decoder thread on the module, which drives a geofence and a relay. The module serves a page and an XML feed over Ethernet back to the panel, a phone and a CSV logger. LAPTOP Control panel local only · self-test satellite view · QR dsflash vendor compiler + bootloader, headless dS3484 MODULE Program flash dScript, 93% full NMEA decoder checksum-gated lock Geofence → relay fixed-point box Web + XML feed 97 declared fields USB SENSOR Xsens MTi-680G GNSS/INS · NMEA GGA · ZDA · HDT · PRDID RS232 Out over Ethernet panel · phone (QR) CSV logger "Declare the region first, report what was observed, claim nothing further."
Fig. 1 · Build and flash in, sensor in, data out

04 Key decisions

Decision 1

Drive the vendor's own compiler, not its GUI

Context
The module is normally built and flashed by clicking through a desktop IDE, which can't be scripted or handed to someone else reliably.
Decision
Link directly against the IDE's compiler and firmware-updater classes and reproduce its compile-and-upload step headlessly, so the bytes written are exactly the bytes the IDE would write.
Trade-off
It's coupled to one version of the vendor's toolchain. Screen-scraping a GUI would have been worse.

Decision 2

Decode on the module, not on a host

Context
Parsing on a laptop is easy, but then the geofence needs the laptop. The point was a board that acts on its own position.
Decision
Run the NMEA decoder as a thread on the module itself, in fixed-point micro-degrees, because the language has no floating point.
Trade-off
No floats, no operator precedence, and program flash at 93%. The board now works with nothing else attached.

Decision 3

A geofence box, not a circle

Context
A radius check means squaring distances, and squared micro-degrees overflow a 32-bit integer.
Decision
Fence with a latitude/longitude box instead, which needs only comparisons and stays exact.
Trade-off
A rectangle (about 45 × 55 m at Cyprus's latitude) instead of a circle. It's correct, and it fits.

Decision 4

The data feed is a contract

Context
At first the feed was whatever the web page happened to show, which hid two diagnostic fields nobody knew existed.
Decision
Write the feed down as a declared contract, generate it, and audit it against the firmware in both directions. The README states "81 of 97 declared" and lists the rest, instead of claiming them.
Trade-off
About 5 KB more website on a nearly full module, for an interface someone can integrate against.

05 Hard problems

A silent sensor with four causes

Nothing came through, and the cause turned out to be four separate faults: an unconnected supply wire, CTS flow control holding every byte, the sensor on the second serial header rather than the first, and left-to-right arithmetic in dScript. That last one put the first fix in the middle of nowhere, at latitude −42.6, longitude −272.8. Each was found and fixed in turn, one variable at a time.

A lock on a single dollar sign

The baud-rate hunt declared a lock as soon as it saw a $, the first byte of every NMEA sentence. Line noise contains plenty of those, so it once "locked" at the wrong speed and served 84 lines of garbage. Now only a sentence whose checksum verifies can declare a lock, and the lock drops after a few seconds without one.

A website that silently disappeared

Twice, the build said success, the upload said OK, and the module served nothing. The first time, a stray zero-byte file was quietly breaking the image, found with three controlled one-variable flashes. The second time, any website above about 470 KB reverted on reboot. The flasher now refuses to write anything over that limit, instead of "succeeding".

06 Outcome & what I'd do differently

Augur works end to end on the bench, with 20–30 satellites in view and a fix accurate to a few metres. It's packaged so another engineer can run it from their own laptop. Still open: validating the logger on hardware, a history view, a geofence that survives a reboot, and RTK, which is waiting on hardware.

  • Declare the contract on day one. The page-coupled feed hid real fields for weeks.
  • Numbers in the docs come from measurement, not from reading the code. The bench disproved two documented claims, and both corrections are written down.
  • Watch the headroom. At 93% program flash, every feature has a cost you only see when it stops fitting.