AdvancedController — удалённое управление по RS485. Описание протокола и интеграция
=====================================================================================
Версия прошивки: r7.2.6. Английская версия: REMOTE_API_EN.txt.


1. ФИЗИЧЕСКИЙ УРОВЕНЬ
---------------------
Порт контроллера: INPUT (RS485, разъём у разъёма питания). Внутри: USART2,
PD5 TX, PD6 RX, PD7 — управление направлением приёмопередатчика.
Параметры линии: 9600 бод, 8 бит данных, без чётности, 1 стоп-бит (9600 8N1).
Полудуплекс, двухпроводная линия A/B. На стороне ПК — адаптер USB-RS485 с
автоматическим управлением направлением (виртуальный COM-порт). Согласующий
резистор 120 Ом на концах длинной линии по правилам RS485.
На этой шине должен быть только ПК: контроллер сам ничего не передаёт, кроме
ответов на команды.


2. КАДР И ПРАВИЛА ОБМЕНА
------------------------
Запрос от ПК:   CMD [ARG1 [ARG2]]<CR><LF>
  - текст ASCII, регистр не важен (br 12 = BR 12);
  - разделитель аргументов — пробел; терминатор — CR, LF или CR+LF;
  - длина строки без терминатора не более 64 символов.
Ответ контроллера на КАЖДЫЙ запрос:
  OK<CR><LF>                    команда выполнена
  OK <данные><CR><LF>           команда выполнена, есть данные (VER, STATUS)
  ERR <код> <текст><CR><LF>     команда не выполнена (см. п. 5)

Стоп-и-жди: ПК отправляет следующую команду только после получения ответа
(или истечения таймаута). Контроллер обрабатывает одну строку за проход
главного цикла; строки, пришедшие раньше, ждут в буфере (256 байт).

Разворот линии: после ответа выдержите не меньше 5 мс перед следующим
запросом. Контроллер переключает приёмопередатчик RS485 обратно на приём
примерно через 2 мс после конца своего ответа; байты, отправленные раньше,
теряются, и на испорченную строку контроллер отвечает ERR 1 UNKNOWN или
ERR 5 CHECKSUM. RemoteClient (lib/remoteclient.cpp) выдерживает эту паузу сам.

Таймауты на стороне ПК (рекомендация):
  обычные команды              0,5 с
  PRESET LOAD/SAVE, KEY LOAD*/SAVE*   3 с
  KEY XH1/XH2, KEY UP/DOWN/LEFT/RIGHT в режиме редактирования перекрестья   2–3 с
  RESET, FOCUS, KEY CAMRESET, KEY FOCUS   10 с
Ответ на долгие команды приходит ПОСЛЕ их выполнения (RESET ≈ 6 с, FOCUS ≈ 5 с).
Во время выполнения долгой команды контроллер не обрабатывает новые строки;
они не теряются и будут обработаны после (ответы придут с задержкой).
Поздний ответ: если ПК не дождался ответа (таймаут), ответ всё равно придёт
позже и может быть принят за ответ на следующую команду. Перед следующей
командой такие строки нужно отбросить (RemoteClient делает это сам: после
таймаута выдерживает 0,3 с тишины и очищает буфер приёма).

Повторы: при отсутствии ответа повторяйте только идемпотентные команды
(PING, VER, STATUS, CAM, BR, AGC, ZOOM, FLIP, MIR, TEMP, LED). Команды KEY,
RESET, FOCUS, PRESET повторять автоматически нельзя — повторная отправка
даст повторное действие.

Ресинхронизация: если между байтами строки пауза больше 100 мс, недостроенная
строка отбрасывается без ответа. Строка длиннее 64 символов — ответ ERR 4
после её терминатора.

Контрольная сумма (необязательно): к запросу можно добавить «*hh», где hh —
XOR всех символов до «*» в шестнадцатеричном виде (стиль NMEA):
  PING*10<CR><LF>   →   OK PONG ADVCTRL*48<CR><LF>
Если запрос содержит сумму, контроллер её проверяет (несовпадение → ERR 5) и
добавляет сумму к ответу. Без «*» суммы нет ни в запросе, ни в ответе.


3. КОМАНДЫ
----------
Все команды со значениями действуют на ТЕКУЩУЮ камеру (см. CAM и STATUS).

  Команда              Аргументы          Действие / ответ
  -------------------  -----------------  -------------------------------------------
  PING                 —                  OK PONG ADVCTRL. Проверка связи и опознание
                                          контроллера (сигнатура «PONG ADVCTRL»).
  VER                  —                  OK r7.2.6. Версия прошивки.
  ID                   —                  OK ADVCTRL FW=r7.2.6 CAMS=4 UID=<24 hex>.
                                          Опознание: прошивка, число камер,
                                          уникальный номер микроконтроллера.
  STATUS               —                  OK CAM=.. BR=.. ... (см. п. 4).
  LEDS                 —                  OK <6 hex>. Состояние 24 светодиодов
                                          клавиатуры, как их показывает
                                          устройство (см. п. 4а).
  CAM n                1..4               Выбрать камеру n (как кнопка CAM SELECT,
                                          светодиоды выбора обновляются).
  BR n                 0..20              Яркость камеры.
  AGC n                0..20              AGC камеры.
  ZOOM n               0..16              Цифровой зум камеры.
  FLIP 0|1             —                  Переворот изображения выкл/вкл.
  MIR 0|1              —                  Зеркало выкл/вкл.
  TEMP 0|1             —                  Показ температуры на OSD камеры выкл/вкл.
  LED n                0..3               Уровень подсветки: 0 выкл, 1 = 25 %,
                                          2 = 75 %, 3 = 100 %. Выключает режим AUTO.
  LED AUTO 0|1         —                  Автоматическая подсветка по входу 0–10 В
                                          выкл/вкл (для всех каналов).
  PRESET LOAD 1|2      —                  Загрузить пользовательский пресет камеры.
  PRESET SAVE 1|2      —                  Сохранить текущие настройки камеры в пресет.
  RESET                —                  Сброс всех камер к настройкам по умолчанию
                                          (кнопка CAM RESET, ≈ 6 с).
  FOCUS                —                  Инициализация перекрестий текущей камеры
                                          (кнопка FOCUS, ≈ 5 с).
  KEY <имя>            см. п. 6           Виртуальное нажатие кнопки клавиатуры.
  IDENTIFY             —                  Светодиод шестерёнки быстро мигает 3 с —
                                          чтобы найти глазами, с каким из
                                          контроллеров работает ПК.

Пример сеанса (терминал, 9600 8N1, CR+LF):
  > PING            < OK PONG ADVCTRL
  > VER             < OK r7.2.6
  > ID              < OK ADVCTRL FW=r7.2.6 CAMS=4 UID=3A0032000E51383433353839
  > CAM 2           < OK
  > BR 12           < OK
  > STATUS          < OK CAM=2 BR=12 AGC=4 ZOOM=0 FLIP=0 MIR=0 TOSD=0 LED=3 AUTO=0 CT=41 PT=36 LT=G XH1=0 XH2=0 CT2=40
  > BR 99           < ERR 3 RANGE
  > HELLO           < ERR 1 UNKNOWN


4. ПОЛЯ STATUS
--------------
  CAM   номер текущей камеры 1..4
  BR    яркость 0..20            AGC   0..20         ZOOM  0..16
  FLIP  0/1                      MIR   0/1           TOSD  температура на OSD 0/1
  LED   уровень подсветки 0..3 (ручной)   AUTO  автоподсветка 0/1
  CT    температура камеры 1, °C (приходит от камеры; до первого сообщения 25)
  CT2   температура камеры 2, °C (поле в конце строки STATUS)
  PT    температура процессора контроллера, °C (внутренний датчик, ориентировочно)
  LT    состояние световой колонны: G — норма (зелёный), R — перегрев камеры (красный)
  XH1, XH2   перекрестья 1 и 2 показаны 0/1
Значения BR/AGC/ZOOM/FLIP/MIR/TOSD/LED хранятся в контроллере отдельно для
каждой камеры и отражают как команды с ПК, так и нажатия кнопок. Значения
самой камеры после PRESET LOAD контроллеру неизвестны (ограничение камеры):
после загрузки пресета задайте нужные величины командами BR/AGC/ZOOM явно.


4а. LEDS — БИТЫ СВЕТОДИОДОВ
---------------------------
Ответ — 24-битная маска в виде 6 шестнадцатеричных цифр (бит = 1: горит).
   0 TEMP      1 XH2       2 XH1       3 SAVE2     4 SAVE1     5 LOAD2
   6 LOAD1     7 CAMMENU   8 FOCUS     9 FLIP     10 MIRROR   11 ZOOM
  12 CAM1SEL  13 UP       14 DOWN     15 RIGHT    16 LEFT     17 ENTER
  18 BRIGHT   19 AGC      20 LEDAUTO  21 LEDON    22 SYSRESET 23 CAM2SEL
Пример: OK 00A000 -> биты 13 (UP), 15 (RIGHT), 17 (ENTER)... считать по маске.
Виртуальная панель (bin/AdvancedControllerPanel) опрашивает LEDS 5 раз в
секунду и повторяет индикацию устройства. CAM1SEL/CAM2SEL — два светодиода
кнопки выбора камеры: камера 1 = 1/0, 2 = 0/1, 3 = 1/1, 4 = 0/0.


5. КОДЫ ОШИБОК
--------------
  ERR 1 UNKNOWN    неизвестная команда
  ERR 2 ARG        неверное число или формат аргументов
  ERR 3 RANGE      аргумент вне диапазона
  ERR 4 TOOLONG    строка длиннее 64 символов
  ERR 5 CHECKSUM   контрольная сумма не совпала


6. ВИРТУАЛЬНЫЕ КНОПКИ (KEY)
---------------------------
KEY воспроизводит нажатие кнопки мембранной клавиатуры и проходит через тот же
обработчик, что и реальная кнопка: те же режимы, светодиоды, блокировки.
Ответ OK приходит после выполнения нажатия.

  ENTER     LONGENTER (долгое — открыть OSD-меню камеры)
  UP  DOWN  LEFT  RIGHT      стрелки (действуют в активном режиме)
  FLIP  MIRROR               переключить переворот / зеркало
  ZOOM  BRIGHT  AGC          включить/выключить режим регулировки стрелками
  XH1  XH2                   показать/скрыть перекрестье 1 / 2
  XH1LONG  XH2LONG           режим редактирования перекрестья (стрелки двигают линии)
  CAMRESET                   сброс всех камер (= RESET); кнопка «три ползунка»,
                             её светодиод мигает во время сброса
  TEMP                       температура на OSD камеры вкл/выкл
  LEDON                      ручной режим подсветки вкл/выкл (стрелки меняют уровень)
  LEDAUTO                    автоподсветка вкл/выкл
  LOAD1 LOAD2 SAVE1 SAVE2    пресеты камеры
  SYSRESET                   выключить все активные режимы регулировки;
                             кнопка-шестерёнка (её светодиод = признак
                             удалённого управления, п. 7)
  CAMSEL                     следующая камера (1→2→3→4→1)
  FOCUS                      инициализация перекрестий (= FOCUS)

Модальность: кнопки ZOOM, BRIGHT, AGC, LEDON, XH1LONG, XH2LONG, LONGENTER
включают режим, в котором стрелки UP/DOWN/LEFT/RIGHT меняют соответствующий
параметр. Одновременно активен один режим регулировки. Для ПО ПК проще
использовать прямые команды (BR, AGC, ZOOM, LED, CAM), а KEY — для того,
чего нет в прямых командах (перекрестья, OSD-меню).


7. ПОВЕДЕНИЕ И ОГРАНИЧЕНИЯ
--------------------------
- Клавиатура контроллера работает параллельно с ПК. Реальное нажатие имеет
  приоритет: виртуальное нажатие выполнится в следующем проходе цикла.
- Изменения, сделанные кнопками, видны ПК через STATUS.
- Камера 4 подключена к контроллеру только подсветкой (ШИМ); команды камере
  при выбранной камере 4 никуда не отправляются (порт INPUT занят ПК).
- Во время RESET/FOCUS контроллер занят до 6 с; сторожевой таймер при этом
  не срабатывает.
- Контроллер не шлёт ничего по своей инициативе.
  Для отображения состояния опрашивайте STATUS, например раз в секунду.
- Индикация оператору: пока с ПК приходят команды, светодиод
  кнопки-шестерёнки на устройстве мигает двумя короткими вспышками с паузой
  2 с; гаснет через 5 с после последней команды. Это делает прошивка;
  ПО ПК ничего для этого не делает (в LEDS это бит 22 SYSRESET — светодиод
  шестерёнки; бит 7 CAMMENU — светодиод кнопки «три ползунка»).
  Команда IDENTIFY заставляет тот же светодиод быстро мигать 3 с (100 мс
  вкл / 100 мс выкл) — так находят, какой из нескольких контроллеров
  подключён к данному порту; потом обычная индикация возобновляется.


8. РЕКОМЕНДАЦИИ ПО ИНТЕГРАЦИИ В ПО КЛИЕНТА
------------------------------------------
1. Один поток (или одна очередь) обмена на порт: строго стоп-и-жди, между
   концом ответа и следующим запросом не меньше 5 мс (см. п. 2).
2. После открытия порта: подождать 100 мс, очистить входной буфер, затем
   ОБНАРУЖЕНИЕ КОНТРОЛЛЕРА: PING с таймаутом 0,3 с, до 2 попыток. Ответ,
   начинающийся с «OK PONG ADVCTRL», означает «контроллер обнаружен»; любой
   другой ответ или тишина — «контроллер не обнаружен» (на порту другое
   устройство или ничего). После обнаружения — ID (версия прошивки, число
   камер, UID микроконтроллера — по нему различают несколько контроллеров).
   Поиск нужного порта среди многих: перебрать все COM-порты той же
   процедурой (RemoteClient::detect(), RemoteClient::findController();
   remote_test --scan / --detect).
3. Каждую команду ждать до таймаута из п. 2; строки, не начинающиеся с OK/ERR,
   игнорировать (тест связи, мусор при включении).
4. Повторять по таймауту только идемпотентные команды (список в п. 2);
   перед повтором отбросить поздний ответ (см. п. 2).
5. ERR — не обрыв связи: показать пользователю и продолжать.
6. Состояние интерфейса ПК синхронизировать по STATUS (раз в 1 с или после
   каждой команды), а не по «предполагаемому» состоянию.
7. При отсутствии ответа на 3 PING подряд — считать связь потерянной,
   переоткрыть порт.
8. Контрольную сумму включать в промышленных условиях (длинная линия, помехи).

C++ / Qt 6 — блокирующий клиент (lib/remoteclient.h), для консольной
программы или рабочего потока:
  #include "remoteclient.h"
  AdvCtrl::RemoteClient rc;
  if (!rc.open("/dev/ttyUSB0")) {
      qWarning() << rc.errorString();
      return;
  }
  if (rc.detect()) {
      AdvCtrl::Fields id;
      if (rc.identify(&id))
          qDebug() << id.value("FW") << id.value("UID");
  }
  rc.cam(2);
  rc.brightness(12);
  rc.led(3);
  AdvCtrl::Fields st;
  if (rc.status(&st))
      qDebug() << "BR =" << st.value("BR").toInt();
  rc.key("XH1");                                // показать перекрестье 1
  if (!rc.brightness(99))                       // контроллер ответит ERR 3 RANGE
      qDebug() << "controller said" << rc.lastReply().code << rc.lastReply().text;

C++ / Qt 6 — приложение с окном (lib/controllerlink.h): клиент работает в
своём потоке, результаты приходят сигналами в поток интерфейса:
  #include "controllerlink.h"
  auto *link = new AdvCtrl::ControllerLink(this);
  connect(link, &AdvCtrl::ControllerLink::stateChanged, this,
          [](AdvCtrl::ControllerLink::State s) { /* Closed, NoLink, Link */ });
  connect(link, &AdvCtrl::ControllerLink::reply, this,
          [](const QString &command, const QString &result) { /* "OK", "ERR 3 RANGE", "!! ..." */ });
  connect(link, &AdvCtrl::ControllerLink::leds, this, [](quint32 mask) { /* 24 бита светодиодов */ });
  link->open("/dev/ttyUSB0");
  link->send("CAM 2");
  link->send("BR 12");
Полный пример использования ControllerLink — виртуальная панель (panel/deviceview.cpp).
Сборка: в своём CMake-проекте найти Qt (find_package(Qt6 REQUIRED COMPONENTS Core
SerialPort) и qt_standard_project_setup()), затем add_subdirectory(<путь>/lib) и
target_link_libraries(<ваша цель> PRIVATE AdvancedControllerRemote) — или
добавить четыре файла из lib/ в свою цель.

Пример на C# (System.IO.Ports.SerialPort):
  var sp = new SerialPort("/dev/ttyUSB0", 9600, Parity.None, 8, StopBits.One);
  sp.NewLine = "\r\n"; sp.ReadTimeout = 500; sp.Open();
  Thread.Sleep(100); sp.DiscardInBuffer();
  string Cmd(string line, int timeoutMs = 500) {
      Thread.Sleep(5);                        // разворот линии (п. 2)
      sp.ReadTimeout = timeoutMs; sp.DiscardInBuffer();
      sp.WriteLine(line);
      while (true) {                          // пропускаем строки, не являющиеся ответом
          string r = sp.ReadLine().Trim();
          if (r.StartsWith("OK") || r.StartsWith("ERR")) return r;
      }
  }
  Console.WriteLine(Cmd("VER"));
  Cmd("BR 12");
  Console.WriteLine(Cmd("STATUS"));
  Console.WriteLine(Cmd("RESET", 10000));

Псевдокод для C/встроенных систем:
  wait_ms(5);                                  /* разворот линии после предыдущего ответа */
  send("BR 12\r\n"); line = read_line(timeout);
  if (starts_with(line, "OK")) ... else if (starts_with(line, "ERR")) code = atoi(line+4);


9. ФАЙЛЫ В ЭТОЙ ПАПКЕ
---------------------
  build.sh              сборка проекта и установка исполняемых файлов в bin/
  run.sh                запуск виртуальной передней панели (bin/AdvancedControllerPanel)
  CMakeLists.txt        проект CMake: lib, panel, tools
  lib/                  библиотека-клиент для встраивания в ПО:
                          remoteclient.h/.cpp    RemoteClient — блокирующий, один порт
                          controllerlink.h/.cpp  ControllerLink — RemoteClient в своём
                                                 потоке, результаты сигналами Qt (окна)
  panel/                виртуальная передняя панель (Qt Widgets): кнопки как на
                        устройстве, светодиоды повторяют реальные (LEDS)
  tools/remote_test.cpp утилита (bin/remote_test): --list, --scan, --detect,
                        --monitor, -c CMD, --selftest, интерактив
  docs/                 этот документ (RU/EN) и справка по кнопкам панели (PANEL_HELP.txt)
  Панель работает с тремя контроллерами одновременно: вкладки CONTROLLER 1..3,
  у каждой свой порт, OPEN/CLOSE и индикатор (серый — порт закрыт, красный —
  порт открыт, контроллер не отвечает, зелёный — связь есть).
Требуется: Linux, Qt 6.5 или новее с модулем Qt Serial Port, компилятор C++17
(GCC или Clang), CMake 3.16 или новее. Порты вида /dev/ttyUSB0, пользователь
должен состоять в группе dialout (sudo usermod -a -G dialout $USER).
