Skip to content

Version 0.4.0: firmware and boot ROM access, safer config writes, faster and sturdier transfers - #48

Merged
cr merged 20 commits into
masterfrom
dev
Oct 1, 2026
Merged

cr merged 20 commits into
masterfrom
dev

Conversation

@cr

@cr cr commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Version 0.4.0. The main addition is firmware flash access: hxtool can now read and write
the firmware of the HX870 family and read its boot ROM. Config writes protect the radio's
identity, transfers follow the vendor tool's line settings and are faster, and GPS log
handling copes with a module that streams, falls silent or sits at the wrong speed.

New

  • hxtool firmware: --readto FILE reads the firmware, --writefrom FILE assesses an
    image against the radio and writes nothing unless --really is given. Images are Motorola
    S-records, as the vendor's updaters hold them; --binary switches to a flat image.
    --reboot restarts the radio when the command went through.
  • hxtool bootrom --readto FILE reads the boot block above the firmware area. There is
    no write for it.
  • Flash mode is recognised on connect, so a second firmware or bootrom run carries on
    with a radio that an earlier run left in flash mode.
  • hx.reboot() and hx.poweroff() in the library.
  • tools/fwextract.py extracts the firmware image from the vendor's updaters for the
    HX870 (02.03, 02.04) and the HX890 (2.00), as S-records and as flat binary. It stands alone.
  • GPX export is GPX 1.0 with speed, course, fix and satellite count (closes Support for GPX 1.1? #30).

Changed

  • Config writes (config --flash) leave the magic, the flash ID and firmware-maintained
    state alone on every model; --force and --force-flashid override (closes Config writer needs Config Memory Magic verification and must never change the device's Flash ID unless forced #10).
  • Line settings follow the vendor tool (115200 baud, DTR low), and reads skip the
    per-chunk status poll on the USB radios: config dumps take half the time.
  • gpslog reads at 9600 baud by default. --fast reads at 115200 and is marked
    unstable. A module that does not answer is looked for at every speed and brought back to
    9600; if it answers at none, the message says to reboot the radio.
  • Logging and progress use rich on a terminal and plain lines elsewhere.
  • Unknown arguments of a subcommand are reported with that subcommand's usage.
  • Housekeeping: Python 3.10 is the floor, ruff replaces the older linters, dead code is
    gone (closes Clean up obsolete files and unused code #14), and the test suite is consolidated. It has 212 tests and never reaches a
    real radio.

For users of the library

  • hxtool.memory.region_code_map is now <Model>Config.REGION_CODES.
  • GenericHXProtocol.get_flash_id() / check_flash_id() moved to <Model>Config.
  • GX1400Protocol.get_firmware_version() is now GX1400Config.firmware_version().
  • write_mmsi(status=) / write_atis(status=) take counter= (an int).
  • config_write(check_region=) is now config_write(force=, write_flash_id=).
  • device.enumerate_devices() returns Candidate named tuples.
  • New: hx.firmware, hx.bootrom, their flash session hx.firmware.p,
    hxtool.srec.Image, GenericHXProtocol.request(), comm.flash_mode.

Verified on radios

  • Firmware read: HX870 (firmware 02.04; identical to the image in the vendor's updater)
    and HX891BT (firmware 1.00).
  • Firmware write: HX870 only. 02.03 was written over 02.04, read back identical, and the
    radio restarted into it. The bytes sent from the erase on are those of the vendor's
    updater in a USB capture of its own 02.03 update.
  • Boot ROM read, flash mode detection, reboot: HX870 and HX891BT.
  • Config dump, IDs, GPS log: HX870 and HX891BT.

Not verified

  • No HX890 has been read or written; its firmware image is known from the updater only.
  • No HX891BT or HX890 has been written.
  • gpslog --fast fails now and then, and after a complete fast transfer the GPS module has
    twice been left silent until the radio was restarted. The cause is not known.
  • The GX1400 has no firmware functions and was not retested.

cr added 20 commits September 30, 2026 22:23
…irmware state; --force and --force-flashid (closes #10); tests never reach real radios
… scaffolding in conftest, convert.py to tools/ (closes #14)
…ltin generics, match in the simulator, single-underscore methods
…re; the library reports progress through callbacks
…tatus poll (dumps twice as fast), vendor write map
…870), always restoring 9600; recover a deaf module
…ransfer fails

CP exchanges skip GPS output, CP mode is recognized while sentences stream in,
and a busy module is waited for instead of having its speed switched. A fast
transfer that does not start (about 1 in 15 on the HX870) restores the speed
and reads at the default one. The simulator paces its log dump to model this.
…st GPS module at any speed

A silent module is looked for at every speed the radio accepts and switched
back to 9600. --fast no longer falls back: a failed transfer is an error with
the module restored and checked, and a module that answers at no speed is
reported with what to do, the log saved if it was complete. The simulator keeps
the radio's and the module's speed apart. README: how the firmware and the
vendor software handle the GPS speed, the firmware update sequence.
tools/fwextract.py extracts the image from the HX870 firmware updaters.
…, model reboot()

FirmwareProtocol runs one flash session (handshake, flash mode, erase,
write, read, status) and hx.reboot() leaves it with its own log line.
--writefrom assesses the image and writes nothing, exiting non-zero on a
failed assessment; --really writes regardless of the assessment.

Flash area 0xF40000..0xFEFFFF, chunk 0x80, for the HX870 and, per the
HX890 updater's write map, the HX890; the HX891BT inherits it. Erase,
blank check and write get the HX890 updater's acknowledgement windows,
and the flash status bits are named.

Read verified on an HX870 with firmware 02.04; write and the HX890
family are simulator-only so far.
…tead of the automatic reboot, model poweroff()

Images are hxtool.srec.Image: address segments, as the vendor's S-records give them. A write sends only the chunks the image has bytes in, a read leaves erased flash out of the file. --binary takes and gives the firmware area as a flat image. A read or write leaves the radio in flash mode; --reboot restarts it at the end, also on its own. hx.poweroff() switches the radio off (#CFLMC 02). tools/fwextract.py writes the updater's S-records and a flat binary.

Read as S-records verified on an HX870 (02.04) and an HX891BT (1.00); a second read from a new connection is identical on both, so flash mode outlasts the connection on the HX870 too. Write, --binary and --reboot through the CLI are simulator-only so far.
…n hxtool.firmware, one request exchange, flash status checked

FirmwareProtocol is the wire level only: the handshake offers the flash ID the radio names, and the status the radio answers #CFLID, #CFLMC 01, erase, blank check and write with is read, acknowledged and an error unless 00. Address and length of flash transfers are range-checked. GenericHXFirmware (hxtool/firmware.py) holds the firmware area, the chunk size and the operations on whole images, model specific things as class attributes; hx.firmware.p is the flash session.

GenericHXProtocol.request() is the one exchange for all # requests, config commands included; their bytes on the wire are unchanged. --reboot restarts the radio only when the command went through. The simulator answers the flash commands with the status, as in the capture of the vendor's update.

Simulator only so far: the changed handshake has not run on a radio.
…mware run carries on with it

A read or write without --reboot leaves the radio in flash mode, where it is silent to the mode handshake and was taken for a radio in NMEA mode. A silent radio is now asked for its flash status; one that answers is in flash mode and gets the firmware handler only. comm.flash_mode is the one flag for it, FirmwareProtocol.active is gone. request() raises when its timeout passes without an answer.

The simulator in flash mode is silent to ? and answers #CMDUN to the application's commands; after #CFLMC 03 it has left CP mode.

Verified on an HX870: binary read, then S-record read with --reboot from a second run, identical images.
…re area

hxtool bootrom --readto FILE [--binary] [--reboot] reads 0xFF0000..0xFFFFFF like the firmware command reads its area, in the same flash session. No write: the vendor's updaters never write the boot block and no erase is known for it.

GenericHXFlashArea holds what firmware and boot ROM share (area, addresses, read_image, image_version); hx.bootrom stands next to hx.firmware. The version is where the model's area names one: the HX891BT's boot block does, the HX870's does not. BootromCommand is the firmware command without the write. The command list is sorted.
The native updater for the HX890 holds the image as a table of four blocks, each byte XORed with a key of 128 bytes that stands before the table and starts anew with every block. fwextract finds the table, decodes the blocks and writes them as S-records (made up, 16 bytes to a record, the flash ID from the end of the area as header) and as flat binary. The HX870 path is unchanged, the script still stands alone.
… at no speed

The state has no known remedy from the host: the radio's GPS sequence restarts on a changed GPS setting, but a config write only reaches the EEPROM (tried on the HX870), and the GPS power pin is out of reach in CP mode. So the message names what works: hxtool firmware --reboot, or off and on. Given both when the module is silent from the start and when it falls silent after a fast transfer.
…rst write on a radio; the assessment gives both sizes of an image

Checked against the USB capture of the vendor's 02.03 update of an HX870: every #CFLWR hxtool sends for that image is byte for byte the vendor's, in the same order; the chunks it leaves out are nothing but 0xFF. The check found hxtool acknowledging the status after erase, blank check and write, which the updater does not, and polling the status twice before the first write. Both are gone; a status the radio repeats because it was not acknowledged is dropped before the next flash request. The handshake keeps its acknowledgements, verified on two radios.

With that, 02.03 was written over 02.04 on an HX870: erase 3 s, 4798 chunks in 148 s, every status 00, read back identical over the whole area, restarted into 02.03. The untested wording is gone.

The area line of the assessment names the bytes of data and, for an image with gaps, the bytes from the first to the last, which is the size of the flat binary.
argparse reported it with the usage of the main parser, which says nothing about what the command takes. The command's own parser reports it now. The command list sorts by name only, so two classes of one name no longer break it. README: the HX891 among the supported models.
@cr
cr merged commit cd89f2d into master Oct 1, 2026
6 checks passed
@cr

cr commented Oct 1, 2026

Copy link
Copy Markdown
Owner Author

@johannessen, do you still have access to the GX1400 for testing the protocol rework?

@johannessen

Copy link
Copy Markdown
Contributor

In principle yes, but it’s currently in storage and I might not have easy access to it until come spring or so.

Furthermore, parts of this update smell to me like LLM output. Even setting aside the obvious ethical questions this would raise if true, I would prefer to review the affected code before I run it against my radio. And while I do see some new things in this update that interest me, given the close to 10000 changes made just yesterday a proper review is likely unrealistic for me.

I’ll consider it, some time.

@cr

cr commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner Author

I hope so much is obvious, and it's the reason I had the time to work through this update. The biggest part was figuring out the protocol dynamics around --fast GPS log transfers at 115kbps which are now possible, but still not entirely stable (and never will be) due to a buggy serial implementation in the firmware.

But all of that including firmware flashing has been extensively tested on my HX870 and HX891. The existing GX1400 support should not have been affecteed by the tweaks to device detection because it requires --model and --tty override anyway. Confirmation would be nice to have, but there's no rush.

@johannessen

Copy link
Copy Markdown
Contributor

It didn’t require --model or --tty with #46, so that would be something to look into during a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants