fix(relays): let the HTTP board's channel count be chosen

The selector offered 1, 2, 4, 8 and 16 and none of them took: the board stayed
at four relays.

The count is decided in three places — deviceRelayCount in Go, chanCount in the
Station Control panel, relayCountUI in Settings. The new type was added to the
first and the dropdown, and to neither of the others, so the panel resized the
labels from a helper that had never heard of it and fell straight through to
its fixed default. The Go side was right the whole time, which is why the
driver would have been built correctly for a count the operator could not set.

All three now know the type. A test pins the Go side against every count the
dropdown offers, and against the fixed boards, whose hardware decides and which
must keep ignoring the field.
This commit is contained in:
2026-08-15 02:35:14 +02:00
parent 26b8dade20
commit 5c958572dc
4 changed files with 46 additions and 4 deletions
+2 -2
View File
@@ -16,7 +16,7 @@
"Appearance: the Sahara theme is lighter, parchment rather than deep sand, with a terracotta accent.",
"UDP: new outbound “Custom message” — you choose what fires it (band change, QSO logged, rotator command, callsign lookup) and what it says, with fields like {band} or {az}. It leaves as a UDP datagram or an HTTP request, which is how most antenna switches are driven.",
"Settings: the UDP section is now “Connections” — it no longer carries only UDP. Every custom message is logged with what was sent, and a message that came out empty says so, with a password in a URL redacted.",
"Station relays: new “HTTP relay” device for home-made and generic switches — an ON and an OFF URL, either one pattern with {relay} or one pair per relay. It then follows the same per-band rules as the named boards."
"Station relays: new “HTTP relay” device for home-made and generic switches — an ON and an OFF URL, either one pattern with {relay} or one pair per relay, with a channel count from 1 to 16. It then follows the same per-band rules as the named boards."
],
"fr": [
"Envoi de spot : le commentaire porte désormais les références de diplôme après le mode — celles que vous avez attribuées (POTA, SOTA, IOTA…), pas le DXCC, la zone et le préfixe que chacun déduit de lindicatif. Un auto-spot porte VOS références dactivation.",
@@ -32,7 +32,7 @@
"Apparence : le thème Sahara s’éclaircit, parchemin plutôt que sable profond, avec un accent terre cuite.",
"UDP : nouvelle sortie « Message personnalisé » — vous choisissez ce qui la déclenche (changement de bande, QSO enregistré, commande de rotor, recherche dindicatif) et ce quelle dit, avec des champs comme {band} ou {az}. Elle part en datagramme UDP ou en requête HTTP, ce qui est la façon dont se pilotent la plupart des commutateurs dantennes.",
"Réglages : la section UDP devient « Connexions » — elle ne porte plus seulement de lUDP. Chaque message personnalisé est journalisé avec son contenu, un message rendu vide le signale, et un mot de passe dans une URL est masqué.",
"Relais de station : nouveau périphérique « Relais HTTP » pour les commutateurs faits main ou génériques — une URL ON et une URL OFF, soit un modèle avec {relay}, soit une paire par relais. Il suit ensuite les mêmes règles par bande que les cartes reconnues."
"Relais de station : nouveau périphérique « Relais HTTP » pour les commutateurs faits main ou génériques — une URL ON et une URL OFF, soit un modèle avec {relay}, soit une paire par relais, avec un nombre de voies de 1 à 16. Il suit ensuite les mêmes règles par bande que les cartes reconnues."
]
},
{
+7 -1
View File
@@ -701,7 +701,13 @@ const MOTOR_BAND_DEFAULT_KHZ: Record<string, number> = {
'40m': 7100, '30m': 10125, '20m': 14150, '17m': 18110,
'15m': 21150, '12m': 24930, '10m': 28400, '6m': 50150,
};
const relayCountUI = (type: string) => (type === 'kmtronic' || type === 'denkovi' ? 8 : 5);
// Fallback only: the rule editor sizes itself from the device's labels, which
// SaveStationDevices already normalises to the real relay count. This is what it
// falls back to when a device somehow has none, so it only needs to be in the
// right neighbourhood — but it is the third place that counts relays, and the
// first two disagreeing is what pinned the HTTP board at four.
const relayCountUI = (type: string) =>
type === 'kmtronic' || type === 'denkovi' ? 8 : type === 'httpgen' ? 4 : 5;
// Live status + OPERATE/STANDBY toggle for ONE configured amplifier (by config
// id) — SPE / ACOM / PGXL alike. Module-scoped (not a nested component) so it
@@ -44,10 +44,16 @@ const TYPE_LABEL: Record<string, string> = { webswitch: 'WebSwitch 1216H', kmtro
// Relay count for a configured device: fixed by type, except Denkovi (4/8) and
// the generic USB-serial board, whose channel count the user picks.
// chanCount is the relay count for a device. It MUST agree with
// deviceRelayCount in app.go: the two decide how many labels and URL rows the
// editor shows and how many relays the driver is built for, and a type known to
// one and not the other silently pins the count to its fixed default — which is
// exactly what happened when the HTTP board was added here and not below.
const chanCount = (d: Device): number =>
(d.type === 'denkovi' || d.type === 'usbrelay') ? (d.channels && d.channels >= 1 ? d.channels : 8)
: d.type === 'dingtian' ? (d.channels && d.channels >= 1 ? d.channels : 2)
: (RELAY_COUNT[d.type] ?? 5);
: d.type === 'httpgen' ? (d.channels && d.channels >= 1 ? d.channels : 4)
: (RELAY_COUNT[d.type] ?? 5);
function blankDevice(): Device {
return { id: '', type: 'webswitch', name: '', host: '', user: '', pass: '', labels: Array(5).fill('') };
+30
View File
@@ -0,0 +1,30 @@
package main
import "testing"
// The relay count is decided in Go and mirrored in the panel. When the HTTP
// board was added to one and not the other, an operator could pick 8 channels
// and still get 4 rows — the selector "did nothing".
//
// This pins the Go side, which is the one the driver is built from.
func TestDeviceRelayCountHonoursTheChosenChannels(t *testing.T) {
for _, typ := range []string{"httpgen", "usbrelay", "denkovi", "dingtian"} {
for _, n := range []int{1, 2, 4, 8, 16} {
if got := deviceRelayCount(StationDevice{Type: typ, Channels: n}); got != n {
t.Errorf("%s with %d channels → %d", typ, n, got)
}
}
}
// Fixed-size boards ignore it: their hardware decides.
for typ, want := range map[string]int{"webswitch": 5, "kmtronic": 8} {
if got := deviceRelayCount(StationDevice{Type: typ, Channels: 3}); got != want {
t.Errorf("%s → %d, want its fixed %d", typ, got, want)
}
}
// Unset falls back to something sensible per type, not to another's default.
for typ, want := range map[string]int{"httpgen": 4, "dingtian": 2, "usbrelay": 8, "denkovi": 8} {
if got := deviceRelayCount(StationDevice{Type: typ}); got != want {
t.Errorf("%s with no choice → %d, want %d", typ, got, want)
}
}
}