feat(cat): Andromeda/G2 front panel support (serial, LED indicators)

Bidirectional Apache Labs Andromeda/G2 front-panel protocol on top of CAT.
Panel logic lives in dedicated units; existing files get only thin hooks.

New units:
- AndromedaMap.pas   — data only: action enums + default button/encoder
  layout tables and LED (ZZZI) numbers (mirroring the Thetis defaults)
- AndromedaPanel.pas — TAndromedaPanel: inbound panel events -> controller
  actions (marshalled via Invoke), state subscription -> ZZZI push via SendProc

Hooks:
- CATEngine: parse ZZZD/ZZZU/ZZZE/ZZZP/ZZZS into generic TCATContext callbacks
- CATAdapter: creates the panel, routes engine callbacks to it, supplies the
  SendProc to the Andromeda port, AddStateListener, ZZZS handshake
- CATSerial: Andromeda flag in port config + HasAndromeda/SendToAndromeda
- RadioController: AddStateListener (multicast, does not steal MainForm's
  OnStateChanged)
- Settings/SettingsForm/MainForm: CATSerialAndromeda[0..3] + persistence + checkbox

First version: serial transport, hardcoded default mapping, LED indicators
(MOX/Tune/CTUN/VFO A-B/Split/NB/NR/SNB/ANF/Squelch). Doc: doc/ANDROMEDA.md.

TODO: default button mapping does not match the physical panel (hardcoded) —
to be fixed later; also ZZMF/LCD text, TCP G2V2 transport, configurable mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 11:40:10 +03:00
co-authored by Claude Opus 4.8
parent 4a92624e9b
commit 2d7290e44d
10 changed files with 704 additions and 9 deletions
+21 -3
View File
@@ -67,6 +67,7 @@ type
const SerDataBits: array of Integer;
const SerStopBits: array of Integer;
const SerParity: array of Integer;
const SerAndromeda: array of Boolean;
TcpEnabled: Boolean; TcpPort: Integer) of object;
TOnAlexChange = procedure(const A: TAlexSettings) of object;
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
@@ -263,6 +264,7 @@ type
FCATSerialDataBits: array[0..3] of TFlatComboBox;
FCATSerialStopBits: array[0..3] of TFlatComboBox;
FCATSerialParity: array[0..3] of TFlatComboBox;
FCATSerialAndromeda:array[0..3] of TFlatCheckBox;
FCATTcpEn: TFlatCheckBox;
FCATTcpPort: TFlatSpinEdit;
@@ -415,6 +417,7 @@ type
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
const SerAndromeda: array of Boolean;
TcpEnabled: Boolean; TcpPort: Integer);
procedure LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0);
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
@@ -2621,7 +2624,7 @@ const
LW = 120;
CX = PAD + LW + 8;
GRP_W = 320;
GRP_H = 240;
GRP_H = 272;
GRP_GAP = 16;
CMB_W = 140;
EDT_W = 160;
@@ -2712,6 +2715,16 @@ begin
Cmb.Items.Add('None'); Cmb.Items.Add('Odd'); Cmb.Items.Add('Even');
Cmb.ItemIndex := 0; // None
FCATSerialParity[i] := Cmb;
Chk := TFlatCheckBox.Create(Self);
Chk.Parent := Grp;
Chk.Caption := 'Andromeda / G2 front panel';
Chk.SetBounds(PAD, R1 + 6 * STEP, GRP_W - PAD * 2, 22);
Chk.Font.Color := CLR_TEXT;
Chk.Font.Name := UI_FONT; Chk.Font.Size := 9;
Chk.Tag := i;
Chk.OnChange := OnCATSerialChange;
FCATSerialAndromeda[i] := Chk;
end;
// TCP server group below the two rows of serial port panels
@@ -2747,6 +2760,7 @@ var
SerEn: array[0..3] of Boolean;
SerPort: array[0..3] of string;
SerBaud, SerData, SerStop, SerPar: array[0..3] of Integer;
SerAndr: array[0..3] of Boolean;
i, Idx: Integer;
begin
if FLoading then Exit;
@@ -2762,9 +2776,10 @@ begin
SerBaud[i] := 9600;
if FCATSerialDataBits[i].ItemIndex = 0 then SerData[i] := 7 else SerData[i] := 8;
if FCATSerialStopBits[i].ItemIndex = 1 then SerStop[i] := 2 else SerStop[i] := 1;
SerPar[i] := FCATSerialParity[i].ItemIndex;
SerPar[i] := FCATSerialParity[i].ItemIndex;
SerAndr[i] := FCATSerialAndromeda[i].Checked;
end;
FOnCATChange(SerEn, SerPort, SerBaud, SerData, SerStop, SerPar,
FOnCATChange(SerEn, SerPort, SerBaud, SerData, SerStop, SerPar, SerAndr,
FCATTcpEn.Checked, FCATTcpPort.Value);
end;
@@ -2782,6 +2797,7 @@ procedure TSettingsForm.LoadCATSettings(
const SerEnabled: array of Boolean;
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
const SerAndromeda: array of Boolean;
TcpEnabled: Boolean; TcpPort: Integer);
var
i, j, Idx: Integer;
@@ -2811,6 +2827,8 @@ begin
end;
if i <= High(SerParity) then
FCATSerialParity[i].ItemIndex := EnsureRange(SerParity[i], 0, 2);
if i <= High(SerAndromeda) then
FCATSerialAndromeda[i].Checked := SerAndromeda[i];
end;
FCATTcpEn.Checked := TcpEnabled;
FCATTcpPort.Value := EnsureRange(TcpPort, 1, 65535);