feat: control the 4O3A Tuner Genius XL directly over TCP (port 9010)

New internal/tunergenius client speaking the same "Genius Series" text API
as the Antenna Genius / PowerGenius XL: banner on connect (with optional
"AUTH" for remote access), "C<seq>|<cmd>\n" commands, "R<seq>|<code>|<msg>"
replies and the "S<seq>|status k=v …" snapshot; async "M|<msg>" info lines
are consumed inline. Polls status ~1.5s and exposes SWR (return-loss dB
converted to a VSWR ratio), forward power (dBm→W), and the operate/bypass/
tuning/active-channel state. Actions: autotune, global bypass, operate/standby.

Controlled directly (not via the radio) so OpsLog uses only one of the box's
four connection slots, per the device's protocol doc.

Wiring:
- app.go: tunergenius.* settings keys, Get/Save/start, GetTunerGeniusStatus,
  TunerGeniusAutotune/SetBypass/SetOperate; started in the background at launch.
- Settings → Tuner Genius panel (enable + IP + optional remote code).
- Docked TunerGeniusPanel widget (SWR/power readouts + Tune/Bypass/Operate),
  top-bar toggle, shown when enabled — mirrors the Antenna Genius widget.
- i18n EN/FR (sec.tunergenius, tg2.*, tgp.*).

Command verbs (operate/bypass/autotune/status/auth) come straight from the
4O3A "Tuner Genius XL — Protocol Description"; UNTESTED on hardware.
This commit is contained in:
2026-07-25 10:32:00 +02:00
parent b4f0e0bc29
commit 933d601c03
10 changed files with 849 additions and 4 deletions
+13
View File
@@ -14,6 +14,7 @@ import {extsvc} from '../models';
import {powergenius} from '../models';
import {spe} from '../models';
import {solar} from '../models';
import {tunergenius} from '../models';
import {winkeyer} from '../models';
import {alerts} from '../models';
import {audio} from '../models';
@@ -474,6 +475,10 @@ export function GetStationStatus():Promise<Array<main.StationDeviceStatus>>;
export function GetTelemetryEnabled():Promise<boolean>;
export function GetTunerGeniusSettings():Promise<main.TunerGeniusSettings>;
export function GetTunerGeniusStatus():Promise<tunergenius.Status>;
export function GetUIPref(arg1:string):Promise<string>;
export function GetUltrabeamSettings():Promise<main.UltrabeamSettings>;
@@ -842,6 +847,8 @@ export function SaveStationDevices(arg1:Array<main.StationDevice>):Promise<void>
export function SaveStationSettings(arg1:main.StationSettings):Promise<void>;
export function SaveTunerGeniusSettings(arg1:main.TunerGeniusSettings):Promise<void>;
export function SaveUDPIntegration(arg1:udp.Config):Promise<udp.Config>;
export function SaveUltrabeamSettings(arg1:main.UltrabeamSettings):Promise<void>;
@@ -922,6 +929,12 @@ export function TestStationDevice(arg1:main.StationDevice):Promise<main.StationT
export function TestUltrabeam(arg1:main.UltrabeamSettings):Promise<void>;
export function TunerGeniusAutotune():Promise<void>;
export function TunerGeniusSetBypass(arg1:boolean):Promise<void>;
export function TunerGeniusSetOperate(arg1:boolean):Promise<void>;
export function ULSStatus():Promise<main.ULSStatusResult>;
export function UltrabeamRetract():Promise<void>;
+24
View File
@@ -902,6 +902,14 @@ export function GetTelemetryEnabled() {
return window['go']['main']['App']['GetTelemetryEnabled']();
}
export function GetTunerGeniusSettings() {
return window['go']['main']['App']['GetTunerGeniusSettings']();
}
export function GetTunerGeniusStatus() {
return window['go']['main']['App']['GetTunerGeniusStatus']();
}
export function GetUIPref(arg1) {
return window['go']['main']['App']['GetUIPref'](arg1);
}
@@ -1638,6 +1646,10 @@ export function SaveStationSettings(arg1) {
return window['go']['main']['App']['SaveStationSettings'](arg1);
}
export function SaveTunerGeniusSettings(arg1) {
return window['go']['main']['App']['SaveTunerGeniusSettings'](arg1);
}
export function SaveUDPIntegration(arg1) {
return window['go']['main']['App']['SaveUDPIntegration'](arg1);
}
@@ -1798,6 +1810,18 @@ export function TestUltrabeam(arg1) {
return window['go']['main']['App']['TestUltrabeam'](arg1);
}
export function TunerGeniusAutotune() {
return window['go']['main']['App']['TunerGeniusAutotune']();
}
export function TunerGeniusSetBypass(arg1) {
return window['go']['main']['App']['TunerGeniusSetBypass'](arg1);
}
export function TunerGeniusSetOperate(arg1) {
return window['go']['main']['App']['TunerGeniusSetOperate'](arg1);
}
export function ULSStatus() {
return window['go']['main']['App']['ULSStatus']();
}