# Поддержка передней панели Andromeda / G2 в EWSDR

Ветка разработки: `feature/cat-commands-expansion`.
Дата последнего обновления: 2026-06-26.

Документ описывает поддержку аппаратной передней панели Apache Labs
**Andromeda / ANAN-G2** (поворотные энкодеры, кнопки, LED-индикаторы, LCD)
через команды CAT, используемые панелью.

См. также [CAT_STATUS.md](CAT_STATUS.md) — общий статус CAT-подсистемы.

---

## 1. Что такое Andromeda и чем отличается от обычного CAT

Andromeda — это **не отдельный набор команд**, а двунаправленный протокол панели
поверх CAT-фрейминга. В отличие от обычного CAT (реактивный «запрос → ответ»),
панель требует, чтобы хост **сам инициативно отправлял** обновления подсветки.

```
            ZZZD/ZZZU/ZZZE/ZZZP/ZZZS  (панель → хост: ввод)
   ┌─────────┐  ───────────────────────────►  ┌──────────┐
   │ Панель  │                                 │  EWSDR   │
   │ G2 /    │  ◄───────────────────────────   │  (хост)  │
   │Andromeda│        ZZZI / ZZMF / ZZZS;       └──────────┘
   └─────────┘  (хост → панель: подсветка, LCD, рукопожатие)
```

| Направление | Команды | Назначение |
|---|---|---|
| **Панель → хост** | `ZZZS<v>` | панель сообщает версию HW/SW (рукопожатие) |
| | `ZZZU<nn>` / `ZZZD<nn>` | энкодер VFO повёрнут вверх/вниз на nn шагов |
| | `ZZZE<eees>` | энкодер #ee повёрнут (01–20 = +шаг, 51–70 = −шаг) |
| | `ZZZP<bbs>` | кнопка #bb: s=1 нажата, 2 long-press, 0 отпущена |
| **Хост → панель** | `ZZZI<nn><s>` | LED-индикатор #nn (00–19) вкл (1)/выкл (0) |
| | `ZZMF<30 цифр>` | текст мультифункц. энкодера на LCD (пока **не реализован**) |
| | `ZZZS;` | запрос версии у панели |

---

## 2. Архитектура реализации

Логика панели вынесена в отдельные юниты; в существующих файлах — только тонкие
хуки. Зависимости однонаправленные, без циклов.

```
                       ┌────────────────────┐
   serial-порт ──────► │ TCATEngine          │  разбор ZZZ* → generic callbacks
   (Andromeda)         │ (CATEngine.pas)     │  (OnPanelVfoStep/Encoder/Button/Version)
                       └─────────┬──────────┘
                                 │ callbacks
                       ┌─────────▼──────────┐    создаёт/связывает
                       │ TCATAdapter         │ ◄───────────────────┐
                       │ (CATAdapter.pas)    │                     │
                       └─────────┬──────────┘                     │
              SendProc (ZZZI/ZZZS;) │   ▲ AddStateListener         │
              на Andromeda-порт     ▼   │ (push индикаторов)        │
                       ┌────────────────────┐         ┌────────────┴───────┐
                       │ TAndromedaPanel     │ ───────►│ TRadioController   │
                       │ (AndromedaPanel.pas)│  Invoke │ (ядро)             │
                       │  использует ▼       │ действия└────────────────────┘
                       │ AndromedaMap.pas    │  (таблицы раскладки — только данные)
                       └────────────────────┘
```

| Файл | Роль |
|---|---|
| **`AndromedaMap.pas`** | Только данные: enum'ы `TAndButton` / `TAndEncoder` / `TAndIndicator`, дефолтные таблицы раскладки кнопок/энкодеров и номера LED (`ZZZI`). Без логики и зависимостей. |
| **`AndromedaPanel.pas`** | `TAndromedaPanel` — «мозг»: входящие события → действия контроллера (свой маршалинг через `FController.Invoke`); подписка на состояние контроллера → построение `ZZZI` → отправка через callback `SendProc`. Зависит только от `RadioController` + `AndromedaMap`. |
| `CATEngine.pas` | Разбор входящих `ZZZD/ZZZU/ZZZE/ZZZP/ZZZS` в 4 generic-callback'а `TCATContext`. Движок остаётся чистым парсером, про Andromeda «не знает». |
| `CATAdapter.pas` | Тонкий клей: создаёт панель, маршрутизирует callback'и движка в неё, даёт `SendProc` на Andromeda-порт, регистрирует панель как слушателя состояния, инициирует рукопожатие. |
| `CATSerial.pas` | Флаг `Andromeda` в конфиге порта + `HasAndromeda` / `SendToAndromeda`. |
| `RadioController.pas` | `AddStateListener` — multicast-наблюдатель состояния (push не «ворует» единственный `OnStateChanged`, занятый MainForm). |
| `Settings.pas` / `SettingsForm.pas` / `MainForm.pas` | Поле `CATSerialAndromeda[0..3]`, персист, чекбокс на каждом serial-порту. |

### Потоки
- Входящие хэндлеры (`HandleVfoStep/Encoder/Button/Version`) зовутся из потока
  CAT-транспорта и маршалят действия в поток контроллера через `FController.Invoke`
  (GUI = `TThread.Synchronize`).
- Слушатель состояния и `SendProc` работают из потока, вызвавшего `Changed()`;
  отправка в порт потокобезопасна (serial `SendStr` под своим локом).

---

## 3. Раскладка по умолчанию (AndromedaMap.pas)

Раскладка **зашита в коде**: внутренний 0-based индекс на единицу меньше номера
кнопки или энкодера, передаваемого панелью. Номера **проверены на реальной панели** (см.
`andromeda_test/AndDecode.pas`). Правится в `AndromedaMap.pas`.

### Кнопки (`ZZZP`, индекс 0-based после разбора)
| # | Действие | # | Действие |
|---|---|---|---|
| 0 | RX1 Mute | 35 | A>B (под shift → 17m) |
| 28 | **Shift** (band-keypad) | 36 | B>A (под shift → 15m) |
| 29 | Band Up (под shift → 160m) | 37 | Split (под shift → 12m) |
| 30 | Mode Up (под shift → 80m) | 38 | *(под shift → 10m)* |
| 31 | Filter Up (под shift → 60m) | 39 | *(под shift → 6m)* |
| 32 | Band Down (под shift → 40m) | 42 | VFO Swap (toggle A/B) |
| 33 | Mode Down (под shift → 30m) | 43 | **VFO Lock** |
| 34 | Filter Down (под shift → 20m) | 44 | CTUN |
| | | 45 | SDR ON (start/stop) |
| | | 46 | MOX |
| | | 47 | TUN |

Не назначены (нет тракта в ewsdr): 2 (RX2 Mute), 40 (band GEN), 41 (RIT/XIT).
NB/NR/ANF/SNB/CTCSS/Squelch у реальной панели на кнопках не выведены;
действия в enum сохранены для пользовательской раскладки.

### Shift / band-keypad

Кнопка #28 — модификатор-защёлка. Пока он активен
(LED `aiShift` #6), кнопки band-группы #29..39 выбирают диапазон напрямую
(`SetBand` 0..10: 160m…6m); #40 = GEN — в ewsdr отсутствует. Снимается
повторным Shift либо автоматически после выбора диапазона (non-sticky).
Логика — в `TAndromedaPanel.HandleButton`.

### Энкодеры (`ZZZE`, индекс 0-based)
| # | Параметр | Шаг |
|---|---|---|
| 0 | AF Gain (громкость) | ±1 |
| 1 | AGC-T | ±1 dB |
| 4 | Filter High → ширина фильтра | ±50 Гц (`AND_BW_STEP_HZ`) |
| 11 | Drive (мощность) | ±1 % |
| VFO | энкодер VFO (`ZZZU/ZZZD`) | ±10 Гц/шаг (`AND_VFO_STEP_HZ`) |

> #4/#5 на панели — независимые края фильтра (Filter High/Low). В ewsdr фильтр
> симметричный (`SetFilterBW`), поэтому #4 крутит ширину, #5 пока не задействован.
> Энкодеры RX2 AF/AGC (#2/#3), diversity (#6/#7), RIT/XIT (#8/#9) — нет тракта.

### Индикаторы (`ZZZI`, номера соответствуют протоколу панели)
| LED # | Индикатор | LED # | Индикатор |
|---|---|---|---|
| 1 | MOX | 12 | Split |
| 2 | ATU Ready *(нет в ewsdr)* | 13 | NB |
| 3 | Tune | 14 | NR |
| 6 | Shift | 15 | SNB |
| 7 | CTUN | 16 | ANF |
| 8/9 | RIT/XIT *(нет)* | 17 | Squelch |
| 10 | VFO A/B | | |
| 11 | VFO Lock | | |

Драйвятся реально: **MOX, Tune, CTUN, VFO A/B, VFO Lock, Shift, Split, NB, NR,
SNB, ANF, Squelch**. Остальные слоты таблицы панели (ATU/RIT/XIT)
в ewsdr функций не имеют и не зажигаются.

---

## 4. Как включить

1. Подключить панель как обычный serial CAT-порт (USB).
2. Settings → CAT → выбрать нужный Serial Port, поставить **Enable** и параметры
   порта (скорость и т.д. — как требует панель).
3. Поставить галку **«Andromeda / G2 front panel»** на этом порту.
4. При применении настроек EWSDR пошлёт `ZZZS;`; панель ответит версией — после
   этого включается push индикаторов и панель управляет радио.

Только **один** порт может быть Andromeda-портом (первый помеченный).

Настройки сохраняются ключами `cat_serial_androm_N` / `ser_androm_N`
(N = 0..3) в JSON-конфиге.

---

## 5. Что сделано / что осталось

### Сделано
- Транспорт: **serial** (USB CAT).
- Вход: `ZZZD/ZZZU` (VFO), `ZZZE` (энкодеры), `ZZZP` (кнопки), `ZZZS` (рукопожатие).
- Выход: **`ZZZI`** — LED-индикаторы (push от `OnStateChanged`, дедуп повторов).
- Раскладка по умолчанию проверена с реальной панелью.
- **Shift / band-keypad** — прямой выбор диапазона (#29..39 → `SetBand`), LED `aiShift`.
- **VFO Lock** — блокировка перестройки (`SetVfoLock`, гейт в `TuneActiveBy`), LED `aiVFOLock`.
- A>B / B>A / Toggle A/B, SDR ON (start/stop) — на штатных кнопках панели.
- UI-чекбокс на каждый serial-порт + персист.

### Осталось / возможные улучшения
| Задача | Заметки |
|---|---|
| **Filter High/Low (`ZZZE` #4/#5)** | независимые края фильтра; сейчас #4 = ширина, #5 не задействован. Нужны `FFilterLow/High` + переработка `ApplyModeFilter` и sideband-aware passband дисплея |
| **Sticky shift** | сейчас shift всегда non-sticky и снимается нажатием кнопки диапазона; можно добавить сохраняемую настройку |
| **`ZZMF`** — текст на LCD мультифункц. энкодера | кодировка: 15 ASCII-символов парами цифр (код−32) |
| **TCP-транспорт (G2 V2 panel)** | сейчас только serial; нужен режим в `CATTcp` |
| **Настраиваемая раскладка** | сейчас зашита; вынести в конфиг и пользовательский интерфейс |
| **Long-press кнопок** | принимается (`ZZZP` state=2), но действий пока нет — обрабатывается только нажатие (state=1) |
| **Индикаторы RIT/XIT/PureSignal/Diversity/ATU** | нет соответствующих функций в `TRadioController` |
| **Мультифункц. энкодер (`aeMulti`)** | назначение не задано |

---

## 6. Точки расширения (чек-лист)

- **Добавить действие кнопки/энкодера:** новый элемент в `TAndButton`/`TAndEncoder`
  (AndromedaMap.pas) + ветка в `TAndromedaPanel.SyncButton`/`SyncEncoder`
  (AndromedaPanel.pas) + правка таблицы `AND_BUTTON_MAP`/`AND_ENCODER_MAP`.
- **Добавить индикатор:** элемент `TAndIndicator` + номер в `AND_INDICATOR_NUM` +
  push в `SyncAllIndicators` и в `OnControllerState` (по нужному `rf*`-полю).
- **Изменить раскладку:** только `AndromedaMap.pas` (таблицы-константы).
- Форматы команд `ZZZ*` и нумерацию элементов проверять по протоколу панели.
