From 16e64fd7b7a85d5a496db75a38919fad29e8efcc Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Wed, 5 Aug 2026 14:49:52 +0200 Subject: [PATCH] fix(ultrabeam): accept an 11-byte status frame from older controllers An older controller behind an RS232-to-Ethernet bridge returns the status frame without its trailing FreqMax byte (11 bytes, not 12). The packet checksum is still valid, so it's a real short frame, not a fragment. queryStatus now accepts >= 10 bytes and defaults the missing FreqMin/FreqMax rather than rejecting it as "too short", which reconnect-looped the link. Whether the byte LAYOUT is otherwise identical across controller versions still needs confirming from the protocol reference. --- changelog.json | 6 ++++-- internal/ultrabeam/ultrabeam.go | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/changelog.json b/changelog.json index ced6c11..b5b0209 100644 --- a/changelog.json +++ b/changelog.json @@ -17,7 +17,8 @@ "Awards: the DXCC list now shows each entity’s main prefix (XE, DL, F…) in its own sortable, searchable column.", "DX Cluster: new option \"Already worked only on the same slot\" (Settings → DX Cluster). With it on, a spot reads \"worked\" only when you worked that callsign on the SAME band and mode — not just anywhere. It respects the digital-mode grouping (Settings → General): grouped, a 20m FT8 contact also marks a 20m FT4 spot as worked; ungrouped, FT8 and FT4 are separate slots.", "DX Cluster: added a WORKED status-filter chip, so you can show — or, with the other chips off, isolate — already-worked spots, not only hide them with the \"Hide worked\" checkbox.", - "DX Cluster: spots that represent nothing — entity/band/mode already worked, the callsign not in your log, no POTA/county/prefix novelty — are now dimmed, so your eye skips them and the spots worth working stand out. What counts as \"nothing\" follows the \"same slot\" option and the digital-mode grouping." + "DX Cluster: spots that represent nothing — entity/band/mode already worked, the callsign not in your log, no POTA/county/prefix novelty — are now dimmed, so your eye skips them and the spots worth working stand out. What counts as \"nothing\" follows the \"same slot\" option and the digital-mode grouping.", + "Ultrabeam: an older controller (seen over an RS232-to-Ethernet bridge) answers with an 11-byte status frame instead of 12. OpsLog rejected it as \"too short\" and reconnect-looped; it now accepts a shorter checksum-valid frame and defaults the missing tail field." ], "fr": [ "Visionneuse de log : la fenêtre conserve deux fois plus d'historique (512 Ko au lieu de 256 Ko, ~3200 lignes). Lors d'une trace chargée, les plus vieilles lignes défilaient hors du buffer pendant qu'on les lisait encore ; la fenêtre agrandie les garde.", @@ -34,7 +35,8 @@ "Diplômes : la liste DXCC affiche le préfixe principal de chaque entité (XE, DL, F…) dans une colonne triable et cherchable.", "DX Cluster : nouvelle option « Déjà contacté seulement sur le même slot » (Réglages → DX Cluster). Activée, un spot n'affiche « contacté » que si vous avez contacté cet indicatif sur la MÊME bande et le MÊME mode — pas juste n'importe où. Elle respecte le groupage des modes numériques (Réglages → Général) : groupé, un contact 20m FT8 marque aussi un spot 20m FT4 comme contacté ; dégroupé, FT8 et FT4 sont des slots distincts.", "DX Cluster : ajout d'un filtre de statut WORKED, pour afficher — ou, en désactivant les autres, isoler — les spots déjà contactés, pas seulement les masquer avec la case « Masquer les contactés ».", - "DX Cluster : les spots qui ne représentent rien — entité/bande/mode déjà faite, indicatif absent du journal, aucune nouveauté POTA/comté/préfixe — sont désormais atténués, pour que l'œil les ignore et que les spots à travailler ressortent. Ce qui compte comme « rien » suit l'option « même slot » et le groupage des modes numériques." + "DX Cluster : les spots qui ne représentent rien — entité/bande/mode déjà faite, indicatif absent du journal, aucune nouveauté POTA/comté/préfixe — sont désormais atténués, pour que l'œil les ignore et que les spots à travailler ressortent. Ce qui compte comme « rien » suit l'option « même slot » et le groupage des modes numériques.", + "Ultrabeam : un contrôleur plus ancien (vu derrière un pont RS232-Ethernet) répond avec une trame de statut de 11 octets au lieu de 12. OpsLog la rejetait comme « trop courte » et bouclait en reconnexion ; il accepte désormais une trame valide plus courte et met par défaut le champ de fin manquant." ] }, { diff --git a/internal/ultrabeam/ultrabeam.go b/internal/ultrabeam/ultrabeam.go index aff3ea5..448c88e 100644 --- a/internal/ultrabeam/ultrabeam.go +++ b/internal/ultrabeam/ultrabeam.go @@ -509,7 +509,13 @@ func (c *Client) queryStatus() (*Status, error) { return nil, err } - if len(reply) < 12 { + // An older controller — seen behind an RS232-to-Ethernet bridge — answers with + // an 11-byte status frame: the standard one WITHOUT the trailing FreqMax byte. + // The packet checksum was already verified, so a short-but-valid frame is real, + // not a fragment. Accept it and default the missing tail fields instead of + // reconnect-looping on "reply too short". reply[9]/[10] (MotorsMoving, FreqMin) + // are the last we truly need, so 10 bytes is the floor. + if len(reply) < 10 { return nil, fmt.Errorf("status reply too short: %d bytes", len(reply)) } @@ -522,8 +528,12 @@ func (c *Client) queryStatus() (*Status, error) { Direction: int(reply[6] & 0x0F), OffState: (reply[7] & 0x02) != 0, MotorsMoving: int(reply[9]), - FreqMin: int(reply[10]), - FreqMax: int(reply[11]), + } + if len(reply) > 10 { + status.FreqMin = int(reply[10]) + } + if len(reply) > 11 { + status.FreqMax = int(reply[11]) } return status, nil