Skip to content

anastas93/satprjct

Repository files navigation

Минимальный модуль передачи и приёма

Минимальный набор компонентов для отправки и приёма сообщений по LoRa с поддержкой шифрования, очередей QoS, веб-интерфейса и управлением через Serial/HTTP. Репозиторий содержит как прошивку, так и вспомогательные библиотеки и средства тестирования.

Содержание

Обзор проекта

В форке сохранены только базовые элементы исходного проекта: очередь сообщений, разбиение и сборка пакетов, радиомодуль SX1262, механизм шифрования ChaCha20-Poly1305 и совместимый режим AES-CCM, а также сопутствующая инфраструктура. Штатная конфигурация рассчитана на работу как на ESP32, так и на ПК.

Прошивка умеет:

  • отправлять и принимать фрагментированные сообщения с подтверждениями ACK;
  • применять код Рида–Соломона, байтовый и битовый интерливинг, свёрточное кодирование и скремблирование;
  • вести журнал статусов, управлять радиопараметрами и обмениваться ключами шифрования;
  • предоставлять веб-интерфейс для чата и настройки устройства.

⚠️ Текущее состояние. В рамках последнего изменения сложная обработка кадров полностью отключена. Транспорт работает по схеме «данные → байты → отправка», а на приёмной стороне полезная нагрузка сразу передаётся в колбэк без дешифрования и ФЕК. Логика подготовки ACK, кодирования, интерливинга и скремблирования сохранена в коде, но выключена директивами препроцессора и комментариями, чтобы в дальнейшем можно было вернуть полный конвейер без переписывания с нуля.

Структура репозитория

  • tx_module.cpp, rx_module.cpp, message_buffer.cpp — ядро обмена сообщениями;
  • radio_interface.h, radio_sx1262.* — абстракция радиоинтерфейса и реализация для SX1262;
  • src/main.cpp — основной скетч PlatformIO с HTTP-интерфейсом, обработкой Serial и интеграцией всех модулей. В файле реализованы вспомогательные функции parseFloatArgument, parseIntArgument, parseBandwidthArgument, parseSpreadingFactorArgument, parseCodingRateArgument, parseChannelArgument и parsePowerPresetArgument, которые проверяют диапазоны числовых HTTP-параметров /cmd и формируют человекочитаемые сообщения об ошибках;
  • web/ — встроенный клиент с вкладками Chat/Antenna helper/Channels/Settings/Security/Debug;
  • key_storage/ — файлы с ключами шифрования;
  • libs/ — сторонние и вспомогательные библиотеки (кодек, интерливинг, логгер и т.д.);
  • tests/ — набор проверок без реального радиоканала;
  • libs_includes.cpp — единая точка подключения реализаций для Arduino;
  • platformio.ini — конфигурация PlatformIO для сборки, прошивки и мониторинга;
  • .vscode/ — рекомендованные настройки VSCode и задач PlatformIO.

Основные компоненты прошивки

Базовые классы

  • MessageBuffer — очередь сообщений с ограничением по количеству элементов и доступом к идентификатору. Позволяет откатывать добавление и просматривать первый элемент без извлечения.
  • ReceivedBuffer — буфер принятых элементов (RAW/SP/GO) с готовыми именами и снимками для команды RSTS.
  • PacketSplitter — делит сообщение на блоки фиксированного размера либо пользовательской длины.
  • PacketGatherer — объединяет части сообщения обратно в единый буфер и отслеживает завершение.
  • FrameHeader — формирует и проверяет заголовок LoRa-кадра, уплотняя флаги, индекс и длину полезной нагрузки в 32-битное поле и полагаясь на AEAD-тег для контроля целостности вместо отдельных CRC.

Модули обработки

  • TxModule — в текущей версии работает как прямой буферизованный передатчик: сообщения добавляются в очередь без префиксов, не кодируются и не шифруются, после чего передаются в радио единым блоком. Историческая логика QoS, ACK, кодирования и скремблирования сохранена в файле, но обёрнута комментариями и #if 0.
  • RxModule — принимает сырые байты от радиомодуля и немедленно пробрасывает их в пользовательский колбэк. Старый конвейер расшифровки, FEC и сборки фрагментов оставлен закомментированным для будущего возврата.

Радиоинтерфейс

  • IRadio — контракт радиомодуля (описан в radio_interface.h).
  • RadioSX1262 — реализация интерфейса с настройкой банка каналов, полосы, SF, CR, мощности и аппаратного режима RX boosted gain, а также методами пинга, маяка и получения последних RSSI/SNR.
    • Вспомогательная функция startReceiveWithRetry() перезапускает приём с несколькими попытками, логирует коды ошибок и выполняет программный сброс SX1262 при занятом канале, чтобы основной цикл продолжал работу без зависаний.
  • Блокировка радиоканала. Все публичные операции RadioSX1262 (send, ping, sendBeacon, ensureReceiveMode, resetToDefaults) теперь возвращают int16_t и защищены мьютексом: на ESP32 используется SemaphoreHandle_t, в тестовой среде — std::timed_mutex. Захват выполняется через lockRadio() с тайм-аутом LOCK_TIMEOUT_MS = 500 мс, итоговый код передаётся вызывающему коду. Значение IRadio::ERR_TIMEOUT (-32000) означает, что радио уже занято, RadioSX1262::ERR_PING_TIMEOUT (-31999) сигнализирует об истечении ожидания ответа, RadioSX1262::ERR_INVALID_ARGUMENT (-31998) — о некорректных параметрах вызова. TxModule, CLI и HTTP-команды проверяют эти коды: повторная отправка планируется позже, в консоль и веб возвращается понятное сообщение radio busy/ERR. Такой контракт фиксирован для будущих задач и описан в коде и тестах.
  • DefaultSettings — флаг DEBUG, уровни логирования, включение RS-кода/ACK и RX boosted gain, пауза между отправками, размер блока для PacketGatherer и значение PING_WAIT_MS.

Вспомогательные библиотеки

  • TextConverter (libs/text_converter/) — преобразует текст между UTF-8 и CP1251, что используется при отправке сообщений и отображении входящих пакетов.
  • SimpleLogger (libs/simple_logger/) — журнал статусов ERR/PROG/GO с кольцевым буфером и единым обработчиком вывода в debug, доступный командой STS.
  • Logger (src/logger.cpp/.h) — асинхронная FreeRTOS-задача Logger::task, которая читает кольцевую очередь, формирует кадры с CRC16 и выводит сообщения в Serial и LogHook. Очередь автоматически усекает слишком длинные записи, считает потери при переполнении и поддерживает немедленную доставку в тестовой среде. Ограничитель пропускает до 80 строк в секунду с начальными 40 токенами, параметры задаются в DefaultSettings::LOGGER_RATE_LINES_PER_SEC и LOGGER_RATE_BURST. Макрос LOG_WRAP(tag, expr) измеряет время выполнения выражения и кладёт CSV-запись tag,start_us,end_us,duration_us в очередь, что позволяет собирать профили критичных участков без блокировок.
  • KeyTransferWaiter (libs/key_transfer_waiter/) — конечная автоматика ожидания кадра KEYTRANSFER RECEIVE, отслеживает тайм-аут, подписчиков (Serial/HTTP) и готовый JSON-результат, позволяя главному циклу опрашивать состояние без блокирующих while.
  • Макросы LOG_ERROR/LOG_WARN/LOG_INFO/DEBUG_LOG и их варианты *_FROM_ISR теперь только формируют строку и отправляют её в очередь логгера; немедленный вывод выполняет задача Logger::task. При необходимости можно включить форсированный Serial.flush() через флаг DefaultSettings::SERIAL_FLUSH_AFTER_LOG (по умолчанию отключён, чтобы не тормозить поток IRQ-логов).
  • HttpBodyReader (src/http_body_reader.h) — шаблонная функция readBodyToVector потоково считывает тело HTTP-запроса в std::vector<uint8_t>, контролирует ограничение по размеру и сообщает о превышении лимита или незавершённом чтении. Используется обработчиком /api/tx-image, чтобы принимать JPEG с нулевыми байтами без промежуточных String.
  • DEBUG_LOG и другие макросы принимают форматированные строки в стиле printf, что упрощает вывод параметров без ручной конкатенации.

Разработка в VSCode и PlatformIO

Проект переведён на рабочий процесс с использованием Visual Studio Code и расширения PlatformIO. Теперь все основные сценарии — сборка, прошивка и мониторинг — доступны прямо из VSCode без Arduino IDE и вспомогательных скриптов.

Подготовка окружения

  • Установите VSCode и расширение PlatformIO IDE (в нём уже есть тулчейн xtensa-esp32-elf, esptool.py и менеджер библиотек).
  • Откройте папку satprjct через File → Open Folder… — PlatformIO автоматически обнаружит файл platformio.ini и настроит рабочее пространство.
  • При первом запуске примите установку рекомендуемых расширений из .vscode/extensions.json, чтобы получить IntelliSense, шаблоны задач и форматирование кода.

Сборка, прошивка и мониторинг

  • Команда PlatformIO: Build (Ctrl+Alt+B) или задача PIO Build собирает прошивку ESP32. Результат по умолчанию складывается в C:\\pio-build\\sat2 (см. опцию build_dir).
  • PlatformIO: Upload прошивает устройство, используя скорость из upload_speed = 921600.
  • PlatformIO: Monitor открывает последовательный порт на скорости 115200 бод (monitor_speed).
  • Для быстрого цикла отладки можно использовать PlatformIO: Upload and Monitor, которая сразу запускает монитор после прошивки.
  • Все эти операции доступны и из терминала: pio run, pio run -t upload, pio device monitor.

Управление зависимостями

  • Сторонние библиотеки объявлены в секции lib_deps файла platformio.ini и размещаются в каталоге libs/, поэтому дополнительных действий в Arduino IDE не требуется.
  • В lib_extra_dirs можно прописать пути к дополнительным Arduino-библиотекам, если они находятся вне репозитория.
  • Параметр build_src_filter исключает из прошивки каталоги tests/, REF/ и содержимое libs/*, чтобы прошивка собиралась только из необходимых файлов.

Настройки рабочей среды

  • В .vscode/settings.json сохранены параметры подсветки, форматирования и IntelliSense для C++17 и Arduino.
  • Доступ к встроенным задачам PlatformIO (Build/Upload/Monitor) осуществляется через палитру команд VSCode (Ctrl+Shift+P).

Если требуется офлайн-сборка на другой машине, достаточно установить Python 3 и PlatformIO CLI (pip install platformio), после чего выполнить команды из раздела «Сборка, прошивка и мониторинг».

Каталог libs/

Каждая библиотека расположена в отдельной подпапке каталога libs/.

Библиотека Назначение
rs255223 Обёртки encode()/decode() для кода Рида–Соломона RS(255,223).
byte_interleaver Байтовый интерливинг (interleave/deinterleave).
conv_codec Свёрточное кодирование и декодирование (encodeBits/viterbiDecode).
bit_interleaver Битовый интерливинг (interleave/deinterleave).
scrambler Скремблирование кадра на основе LFSR (полином x^16 + x^14 + x^13 + x^11, seed 0xACE1).
frame Структуры заголовков кадров (FrameHeader).
key_loader, crypto/aes_ccm, key_transfer Работа с ключами, AES-CCM и обмен корневым ключом.
crypto/hkdf HKDF-SHA256 для вывода симметричных ключей и вспомогательных параметров.
simple_logger, text_converter Логирование, преобразование текста.
serial_mirror Зеркалирует вывод базового Serial в LogHook, предоставляет метод availableForWrite(), который напрямую делегирует HardwareSerial::availableForWrite() и сохраняет совместимость с конструкциями вида while (!Serial).
config_loader Читает файл config/default.ini, подставляет значения по умолчанию и предоставляет доступ к структуре настроек.

SerialMirror

  • Метод SerialMirror::availableForWrite() возвращает количество байт, которое базовый HardwareSerial готов принять для записи без блокировок, что позволяет корректно использовать проверку заполнения буфера перед дампом RX-данных.
  • Функция dumpRxToSerialWithPrefix() (файл src/rx_serial_dump.h) безопасно выгружает RX-пакет в Serial, фиксируя предупреждение «RX dump truncated» через LogHook, если буфер порта не вместил все данные.

Файл libs_includes.cpp подключает реализации библиотек для Arduino-проекта и избавляет от неявных зависимостей. Сборкой и прошивкой управляет platformio.ini, поэтому отдельные скрипты для arduino-cli больше не требуются.

Веб-интерфейс

Каталог web/ содержит встраиваемый интерфейс с вкладками Chat, Channels/Ping, Settings, Security и Debug. Страница отдаётся прошивкой напрямую из web/web_content.h, который формируется скриптом tools/generate_web_content.py из файлов web/index.html, web/style.css, web/script.js, web/libs/sha256.js, web/libs/mgrs.js, web/libs/freq-info.csv и web/libs/geostat_tle.js. Для раздачи справочника частот и преобразования координат используются обработчики handleFreqInfoCsv и handleMgrsJs в src/main.cpp, чтобы документация соответствовала реальной реализации веб-сервера. Команда: python src/tools/generate_web_content.py --output src/web/web_content.h

Обновление веб-ресурсов

  • Редактируйте HTML/CSS/JS в каталоге web/, после чего выполните python3 tools/generate_web_content.py, чтобы пересобрать web/web_content.h перед сборкой прошивки.
  • Скрипт tools/generate_geostat_tle.py обновляет web/libs/geostat_tle.js из файла REF/tle.txt; для встраивания изменений запустите генератор веб-контента повторно.

Сборка прошивки

  • Используйте задачи PlatformIO (Build, Upload, Upload and Monitor) либо CLI-команды pio run, pio run -t upload, pio device monitor. Конфигурация (скорость порта, разделы флеша, флаги компилятора) задаётся в platformio.ini.

Общие возможности

  • Единый адаптивный дизайн с навигацией, раскрывающейся поверх контента на мобильных устройствах.
  • При переключении вкладок прокрутка блокируется, а активная вкладка подсвечивается.
  • История сообщений и настройки сохраняются в localStorage; при его недоступности используется резервное хранение в памяти вкладки.
  • Если интерфейс открыт в нескольких вкладках, история чата и состояние ключей синхронизируются через localStorage, поэтому область chat-area, keyPublic, keyId и keyBackup обновляются без перезагрузки страниц.
  • Внизу страницы отображается подпись «Powered by AS Systems» с номером версии из /ver. После успешного чтения значение кэшируется в localStorage, поэтому отображается даже при временном отсутствии связи. Дополнительно используется команда VER, а ответы unknown/пустые строки очищают кэш и скрывают значение. Файл ver хранит версию в кавычках, поэтому прошивка получает резервное значение даже при отсутствии внешнего файла.
  • Переключатель темы поддерживает «красный» ночной режим и автоматическое включение по времени; автоматический режим можно отключить.
  • Блок состояния показывает флаги ACK и шифрования: индикаторы работают как переключатели.

Push-уведомления (SSE)

  • Прошивка, собранная из этого репозитория, поднимает SSE-эндпоинт /events, который отдаёт краткие JSON-уведомления о появлении новых элементов в ReceivedBuffer. Сообщение содержит тип (raw/split/ready), имя, идентификатор и размер полезной нагрузки.
  • Канал /events дополнен событием log, передающим JSON {id, uptime, text} с новыми строками Serial-журнала, поэтому вкладка Debug получает обновления в реальном времени без дополнительного опроса.
  • Веб-интерфейс автоматически подключается к каналу: при активном push обновления попадают в чат сразу, а периодический запрос RSTS переводится в резервный режим (30 с). Если SSE недоступен (старая прошивка, неподдерживаемый браузер, временный обрыв), приложение возвращается к классическому опросу каждые 5 с без вмешательства пользователя.
  • При ошибке создания SSE-подключения клиент сохраняет причину сбоя, показывает её на вкладке Debug и автоматически планирует повторное подключение с увеличением задержки (до 30 с). После смены адреса в настройках счётчики сбрасываются, и страница сразу пытается открыть новый канал поверх актуального эндпоинта.
  • Отладочная вкладка Debug показывает текущий режим («push активен»/«используется опрос»), а также напоминает, что push требует обновлённой прошивки с поддержкой SSE.

Вкладка Chat

  • Пузырьковое отображение истории сообщений с цветами для исходящих, входящих и системных сообщений.
  • Команды можно отправлять напрямую через /, аргументы (/ACK 0, /TX текст) сохраняются в истории.
  • В чате появился модуль обмена изображениями:
    • кнопка с пиктограммой и drag&drop принимают JPEG/PNG/HEIC размером до 20 МБ;
    • браузер масштабирует изображение в рамку 320×240 с сохранением пропорций (letterbox) и по умолчанию переводит его в градации серого;
    • профили S/M/L управляют качеством JPEG (≈0.30/0.40/0.40) и цветностью, отправка выполняется через POST /api/tx-image, а прошивка отклоняет кадры больше 20 480 байт (160 слотов × 128 байт);
    • части изображения маркируются индексами IMGP-XXXXXX, собранные кадры IMG-XXX кэшируются и появляются в истории чата с подписью.
  • Системные ответы выводятся курсивом уменьшенного кегля, подтверждение TX отображается внутри сообщения отправителя.
  • Кнопки BCN, TESTRXM и блок отправки TXL вызывают служебные команды без ввода вручную.
  • Полученные пакеты автоматически дублируются в чат: основное тело показывает полезную нагрузку из поля text, а под ним выводятся подписи name и Len в байтах. При восстановлении браузер пробует несколько кодировок и использует эвристики для борьбы с «кракозябрами» (СЃР±…), поэтому кириллица отображается корректно даже после двойного перекодирования. Старые записи с тегом rx-name нормализуются в формат rx-message.
  • Если устройство сперва вернуло только имя GO-xxxxx, чат автоматически обновит запись и подставит текст сразу после появления полезной нагрузки в ответе RSTS.
  • История чата автоматически ограничивается 500 сообщениями: функции applyChatHistoryLimit, adjustChatProgressAfterTrim и adjustChatDomAfterTrim из web/script.js обрезают избыточные записи, синхронизируют DOM-индексы и корректируют карту прогресса, чтобы сериализация и прогресс приёма оставались консистентными.
  • Фоновый монитор входящих автоматически подключается к SSE-каналу /events и получает push-уведомления о новых GO-xxxxx/SP-xxxxx/R-xxxxxx. При активной подписке чат обновляется мгновенно, а резервный опрос RSTS запускается раз в 30 с. Если прошивка не поддерживает SSE или соединение недоступно, веб-интерфейс сам возвращается к прежнему режиму опроса каждые 5 с, поэтому совместимость со старыми версиями сохраняется.
  • Парсер ответов RSTS теперь распознаёт вложенные JSON-структуры (ready/split/raw, массивы байтов) и корректно восстанавливает полезную нагрузку даже при изменённом формате прошивки, поэтому входящие сообщения снова появляются в чате сразу после приёма.
  • Если полезная нагрузка отсутствует, пузырь чата показывает имя пакета и вычисленный объём данных (GO-xxxxx · 42 байт), чтобы было видно, что ответ собран полностью.
  • Пока в списке RSTS остаются промежуточные пакеты SP-, в левом нижнем углу истории чата отображается анимированный индикатор «Приём сообщения…», который гаснет сразу после сборки сообщения.
  • Под окном истории расположен стеклянный статус-бар «Последний IRQ»: функция renderChatIrqStatus() в web/script.js реагирует на строки журнала RadioSX1262: IRQ=... (уровень DEBUG), показывает текст события и при наличии дописывает uptime устройства. Так операторы видят актуальные аппаратные флаги, не переключаясь во вкладку Debug.
  • При появлении SP-xxxxx в списке входящих в чате создаётся отдельный пузырь «Приём пакета GO-xxxxx», отображающий объём уже полученных данных. Как только приходит финальный пакет GO-, индикатор прогресса превращается в обычное сообщение с полезной нагрузкой и убирается из списка ожидающих.
  • Текст готовых сообщений GO-xxxxx восстанавливается из hex прямо в браузере (функции hexToBytes, decodeBytesToText, resolveReceivedText), поэтому в списке и в чате сразу отображается собранный payload, даже если прошивка отдаёт только шестнадцатеричный снимок.
  • Команда TESTRXM формирует серию из пяти сообщений на основе фразы Lorem ipsum: четыре варианта содержат 100/50/30/20 % текста, а пятое проходит через эмуляцию PacketGatherer (создаются записи R-/SP-/GO-); задержка между генерациями составляет 0,5 секунды.
  • Функция pollReceivedAfterTestRxm() автоматически опрашивает RSTS и подхватывает готовые сообщения после запуска TESTRXM, добавляя их в чат без повторов благодаря отслеживанию последнего имени пакета.
  • В чат автоматически попадают все элементы со статусом ready (не только GO-xxxxx), а промежуточные SP-/R- остаются в списке входящих — так легче отслеживать историю сборки сообщений.
  • Логика распознавания статуса вынесена в функцию normalizeReceivedType(), чтобы без регулярных выражений унифицировать работу с типами пакетов.
  • При прокрутке истории вверх появляется плавающая кнопка «вниз», возвращающая к последнему сообщению без неожиданных скачков; если пользователь читает старые сообщения, новое сообщение не перехватывает фокус.
  • Входящие записи с ролью rx сопровождаются коротким звуковым уведомлением через Web Audio — звук активируется после первого взаимодействия и помогает не пропустить ответ.
  • В навигации добавлен бейдж непрочитанных сообщений: функция updateChatUnreadBadge() читает и сохраняет счётчик в localStorage, анимацией напоминает о новом сообщении и автоматически выключает aria-live, когда вкладка чата активна.
  • Блок быстрых команд и отправка TXL свернуты в <details>, что экономит место на мобильных устройствах и не перегружает интерфейс.

Вкладка Antenna helper

  • Бывшая вкладка Pointing переименована в «Antenna helper» и отражает обновлённую структуру интерфейса.
  • Верхняя сводка отображает состояние TLE, введённые координаты наблюдателя и количество видимых спутников. Источник координат теперь всегда «ручной ввод».
  • Панель наблюдателя стала складывающейся: внутри указываем квадрат MGRS (100 км) и высоту. По умолчанию используется сектор 43U CR; введённое значение мгновенно нормализуется и сохраняется в кеше браузера.
  • Геолокация и датчики ориентации удалены, поэтому интерфейс больше не запрашивает разрешения и не привязан к HTTPS. Все подсказки про сенсоры и компас заменены на инструкции по ручному вводу.
  • Панель горизонта получила разметку основных азимутов и компактные маркеры спутников. Цвет совпадает с квадрантом компаса, маркеры занимают на 50 % меньше места и автоматически разводятся по вертикали при плотном расположении аппаратов; расстояние до горизонта ближе к реальному.
  • Круговой радар развёрнут так, что юг расположен в верхней части (как при наведении антенны), а на циферблат добавлены подсвеченные сектора и подписи всех сторон света. Легенда по-прежнему повторяет цветовые группы квадрантов.
  • Ручной ввод координат MGRS стал устойчивее: высота обновляется без повторного ввода квадрата, допустимы пробелы и запятые, а сохранённое значение высоты применяется сразу.
  • Если библиотека /libs/mgrs.js недоступна, вкладка выводит предупреждение, блокирует ввод и сохраняет последнее введённое значение до восстановления конвертера.
  • Список спутников сгруппирован по квадрантам; карточки показывают азимут, возвышение, долготу подспутника и другие параметры. Функции выбора и подсветки синхронизированы с панелью горизонта и радаром.
  • Преобразование MGRS в широту/долготу выполняет новая библиотека web/libs/mgrs.js, которая восстанавливает центральные координаты 100‑км квадрата.

Вкладка Channels/Ping

  • Таблица частот активного банка показывает частоты, RSSI, SNR, статус и результат сканирования.
  • Текущий канал подсвечивается, строки получают классы busy, free или unknown.
  • При клике открывается карточка с измерениями и справочными данными (System/Band Plan/Purpose). Данные берутся из web/libs/freq-info.csv, при недоступности — из встроенной копии.
  • Stability test сохраняет для каждого пакета задержку, причину ошибки и обновлённые метрики RSSI/SNR; сводка показывает успешность, среднюю задержку, джиттер и диапазоны значений.
  • График Stability test получил подсказки по наведению, отображает средние значения, диапазоны RSSI/SNR и подсвечивает точки провалов; карточки под графиком помогают быстро оценить разброс метрик.
  • История Stability test хранится в браузере по каналам: отображаются последние прогоны с трендом успешности, доступен экспорт в CSV/JSON и очистка истории.
  • В верхней части вкладки выводится индикатор состояния справочника: он показывает загрузку CSV, предупреждает об использовании fallback и сообщает о реальной ошибке запроса.
  • Последний успешно полученный список каналов кешируется в localStorage: при тайм-ауте запросов отображаются реальные частоты вместо демонстрационных значений, а после восстановления связи данные автоматически перезаписываются актуальным ответом устройства.
  • Карточка содержит кнопку «Установить текущим каналом» для мгновенного переключения радиомодуля.
  • Кнопка Search последовательно пингует каналы активного банка, выделяя состояния классами scanning, signal, crc-error, no-response. Результаты можно экспортировать в CSV с полем scan_state.
  • В карточке канала появились тесты Stability test и CR test. Первый выполняет 20 ping с интервалом 300 мс, строит график RSSI/SNR и считает процент успешно доставленных пакетов. Второй перебирает все значения CR, выполняет ping для каждого и выводит таблицу с результатами и измеренными RSSI/SNR.
  • Обновление статусов во время сканирования выполняется через вспомогательную функцию uiYield() (requestAnimationFrame или setTimeout). Дополнительные функции: detectScanState(), applyPingToEntry(), setChannelScanState().

Вкладка Settings

  • Отдельная секция «Подключение» содержит поле Endpoint (HTTP) с подсказкой по формату. Значение сохраняется в localStorage, а при изменении интерфейс немедленно инициирует повторный опрос радиомодуля без перезагрузки страницы.
  • Поддерживает ввод Endpoint, управление BANK/BF/CH/CR/PW/RXBG/SF и настройку количества повторов ACKR (при включённом подтверждении), паузы между отправками (PAUSE), тайм-аута ACK (ACKT) и задержки ответа ACK (ACKD). Значение ACKT = 0 выключает ожидание ACK: отправленный пакет сразу считается доставленным, очередь не блокируется ожиданием подтверждения. Дефолтное значение тайм-аута составляет 320 мс, что синхронизировано между прошивкой и веб-интерфейсом.
  • Переключатель RXBG управляет аппаратным режимом повышенного усиления приёмника; подсказка рядом показывает текущее состояние, считанное с устройства.
  • Переключатель «Light pack — прямой текст» переводит передачу в минимальный режим: текст из чата уходит напрямую в LoRa-модуль «сырым» пакетом без заголовка и служебного префикса, а принятые кадры отображаются без дополнительных полей.
  • Секция «Ключ шифрования» содержит кнопки LOCAL и PEER, которые вызывают команды KEYGEN и KEYGEN PEER напрямую из настроек. Кнопка PEER автоматически блокируется, если нет сохранённого публичного ключа партнёра, а подсказка поясняет текущий режим.
  • Значения считываются с устройства при загрузке страницы, выбранный канал сохраняется в локальном хранилище и восстанавливается после перезапуска.
  • Под выпадающим списком отображаются частоты RX/TX активного канала, подсказки рядом с ACKR, ACKT и ACKD отображают актуальные задержки и тайм-ауты.
  • Подсказка рядом с ACKR отображает суммарное ожидание перед повторной отправкой с учётом тайм-аута и задержки формирования ACK.
  • Поле «Сообщение для TESTRXM» позволяет задать текст для теста входящих сообщений (до 2048 символов, ~2 КиБ); при пустом значении используется встроенный шаблон Lorem ipsum.
  • Переключатель «Тестовый режим TX/RX» активирует эмуляцию SendMsg_BR/received_msg: введённые через чат сообщения кодируются по старому формату, сохраняются в буфере и сразу отображаются без обращения к реальному радиоканалу. Состояние можно запросить и отключить из настроек.

Вкладка Security

  • Управление ключами (KEYSTATE, KEYGEN, KEYRESTORE, KEYTRANSFER SEND/RECEIVE). Все операции дублируются в отладочный журнал.
  • Реализация SHA-256 (web/libs/sha256.js) позволяет вычислять хеш ключа в среде без WebCrypto.
  • Переключатель включает принудительное шифрование (даже для старых кадров без флага).
  • Кнопка KEYTRANSFER SEND копирует публичный ключ в буфер обмена (при поддержке API браузера); KEYTRANSFER RECEIVE сопровождается мигающим индикатором ожидания.
  • Неблокирующее ожидание KEYTRANSFER RECEIVE реализовано функциями clearKeyReceivePollTimer, resetKeyReceiveWaitState, updateKeyReceiveWaitMessage, scheduleKeyReceivePoll и pollKeyTransferReceiveStatus в web/script.js: первый ответ status="waiting" запускает фоновый поллинг состояния, обновляет текст статуса и удерживает кнопку в режиме ожидания до получения окончательного JSON.
  • Ответ KEYSTATE дополнительно содержит поля baseKey, safeMode и storageReady: базовый симметричный ключ (hex), признак защищённого режима и готовность хранилища.
  • Над блоком кнопок выводится строка индикаторов «Ключ / Хранилище / Safe mode», которая меняет цвет при использовании базового ключа, отключении хранилища или активации safe mode и поясняет текущий статус отдельным текстовым сообщением.
  • Функция parseJsonLenient в web/script.js сначала пытается очистить и нормализовать ответ KEYSTATE, убирая висячие запятые и комментарии, а затем передаёт его в JSON.parse.
  • Функция parseKeyStateFallback в web/script.js разбирает нестрогие ответы KEYSTATE (без корректного JSON), извлекая тип ключа, идентификаторы и признак резервной копии.
  • Функция deriveKeyIdFromHex в web/script.js вычисляет ID ключа из HEX-представления через sha256Bytes, чтобы сравнить активное значение с базовым и подсветить ситуацию, когда новый ключ ещё не применён.
  • Блок ENCT показывает исходные данные, шифртекст, тег, nonce и результат дешифрования.
  • При активации защищённого режима (отказ KeyLoader::ensureStorage()) веб-интерфейс показывает safeMode = true, отключает управление шифрованием и сообщает о необходимости повторной инициализации команды KEYSTORE RETRY после устранения причин.

Вкладка Debug

  • Выводит отладочные сообщения, включая операции с ключами и ответы сервера.

  • Поддерживает видимое выделение фокуса для ссылок и кнопок.

  • Кнопки RSTS FULL и RSTS JSON выполняют запросы списка сообщений с разным форматом вывода и пишут подробный ответ в отладочный лог.

  • Доступна выгрузка полного списка сообщений (RSTS full json=1) в файл rsts-full-<timestamp>.json одной кнопкой.

  • Кнопка Экспорт TXT сохраняет текущий видимый журнал Debug в текстовом файле с отметками времени, типом события, источником и счётчиком повторов.

  • Карточки журнала стали компактнее: уменьшены отступы, бейджи переносятся на новую строку без наложения, улучшена читабельность на узких экранах.

  • Фоновая панель показывает суммарный объём принятых данных (Принято данных) и количество полностью собранных сообщений: счётчик строится по уникальным пакетам ready, а человекочитаемое значение выводится через formatBytesBinary.

  • В верхней части размещён блок «Мониторинг опроса RSTS», который показывает текущий интервал, длительность последнего запроса, экспоненциально усреднённое время ответа, число ошибок/таймаутов и случаи блокировки из-за параллельных обращений. Индикатор автоматически обновляется каждую секунду, помогая быстро обнаружить задержки или зависание фонового опроса.

  • Рядом размещён блок «Использование памяти браузера»: он использует API performance.memory или measureUserAgentSpecificMemory, отображает текущий размер JS-кучи, лимит, тенденцию за последние минуты и позволяет вручную обновить значения. При отсутствии поддержки браузером выводится пояснение и ориентировочный объём памяти устройства по navigator.deviceMemory.

  • При открытии вкладки выполняется запрос /api/logs, который подтягивает свежую историю Serial, а затем карточка debug-log пополняется push-событиями log практически без задержки.

  • Любые вызовы Serial.print/Serial.println, включая вспомогательные макросы и прямой вывод, автоматически зеркалируются в буфер LogHook, поэтому журнал Debug всегда содержит те же сообщения, что и физический UART; непечатные байты конвертируются в компактные HEX-блоки с префиксом [BIN].

  • Функции enqueueLogEntry и flushPendingLogEntries складывают новые строки в небольшую очередь и выгружают их в SSE-клиентов из loop(), избавляя USB-Serial от пауз при длительной отправке push-сообщений.

  • Для отправки SSE-событий добавлен буфер SseBufferedWriter, который дробит кадры на куски в соответствии с доступным окном TCP, отслеживает тайм-ауты ожидания освобождения буфера и защищает основной цикл от блокировок при медленных клиентах.

  • SSE-канал /events теперь отдаёт событие irq с последней расшифровкой флагов SX1262, поэтому блок «Последний IRQ» обновляется даже если зеркалирование Serial отключено или уровень логов поднят до WARN.

  • Журнал debug-log перестаёт автоматически прокручиваться, если пользователь пролистал историю вверх; возврат к последним записям снова закрепляет прокрутку у конца списка.

  • Функция formatTimeOfDay приводит отметки времени журнала и чата к 24-часовому формату с секундами, устраняя расхождения при системной локали с 12-часовым отображением.

  • Функция logErrorEvent в web/script.js агрегирует одинаковые сетевые ошибки и обновляет последнюю запись счётчиком повторов, поэтому временная недоступность устройства больше не заполняет журнал десятками одинаковых строк.

HTTP API

  • POST /api/tx — отправляет текст через TxModule (используется кнопкой «Отправить по радио»).
  • POST /api/tx-image — принимает JPEG (320×240) с заголовками X-Profile/X-Name, добавляет его в очередь бинарных сообщений и передаёт в ESP32 без дополнительной обработки.
  • GET /cmd?c=<CMD> и GET /api/cmd?cmd=<CMD> — выполнение команд (PI, SEAR, BANK, CH, CHLIST, STS, RSTS, RXS, INFO, VER). Параметры передаются через v или bank.
  • HTTP-команды для настройки: BF, SF, CR, PW, RXBG, PAUSE, ACK, LIGHT, ACKR, ACKT, ACKD, RXSTAT, BCN, TXL, TX, TESTMODE (0/1/toggle — управление тестовым режимом TX/RX).
  • ACKD управляет задержкой отправки подтверждения (0–5000 мс) — значение передаётся в TxModule::setAckResponseDelay().
  • RXS выводит сводку rx.dropStats() — суммарный счётчик отброшенных кадров и топ причин. Параметр top (n, limit) ограничивает размер выборки (по умолчанию 5), fmt=json (json=1) переключает формат. RXS RESET мгновенно обнуляет накопленные значения и возвращает пустую статистику (флаг reset=true добавляется в JSON). Команда доступна и через Serial (RXS, RXS JSON, RXS RESET).
  • RXSTAT отдаёт агрегированную статистику дропов и последние конфликты индексов фрагментов (fragmentMismatchHistory()), позволяя оперативно выявлять разрывы последовательности.
  • GET /libs/geostat_tle.js — отдаёт встроенный список TLE для офлайн-работы вкладки Antenna helper.
  • GET /libs/mgrs.js — возвращает библиотеку преобразования квадрата MGRS в координаты для вкладки Antenna helper.
  • GET /libs/freq-info.csv — отдаёт справочник частот, используемый вкладкой Channels/Ping (совпадает с CSV из web/libs).
  • GET /api/logs?n=<count> — возвращает JSON \"logs\" с массивом объектов {id, uptime, text} для инициализации вкладки Debug.
  • Команды работы с ключами возвращают JSON (KEYSTATE, KEYGEN, KEYRESTORE, KEYTRANSFER SEND, KEYTRANSFER RECEIVE, а также совместимые KEYSEND/KEYRECV).
  • GET /api/cmd?cmd=KEYRECV&peer=<HEX> — передаёт публичный ключ напарника в cmdKeyReceiveSecure; параметр peer заменяет устаревший псевдоним pub, который продолжает поддерживаться для обратной совместимости.
  • Ответ RSTS full=1 включает список элементов с полями name, type, len, text, hex.

Интерпретация вывода RXSTAT

RXSTAT доступен одновременно по Serial и HTTP (fmt=json или json=1 переключают формат). Помимо суммарных счётчиков из dropStats() команда возвращает массив fragmentMismatches с полями:

  • msgId — идентификатор сообщения, к которому относится конфликт.
  • expected — индекс фрагмента, который ожидал RxModule.
  • actual — индекс, полученный фактически.
  • fragCnt — сколько фрагментов заявлено в кадре.
  • ageMs — время, прошедшее с момента фиксации (милисекунды).

Рекомендации по устранению:

  1. Если actual сбрасывается в 0, передатчик начал новое сообщение до завершения текущего — проверьте порядок вызовов TxModule::queue() и наличие повторных отправок из-за тайм-аутов ACK.
  2. Когда actual > expected, вероятна потеря одного или нескольких фрагментов. Стоит повысить класс QoS сообщения или увеличить паузу PAUSE, чтобы дать радиоканалу стабилизироваться.
  3. При многократных повторениях одного msgId убедитесь, что приложение не переиспользует идентификаторы и не сбрасывает очередь TxModule во время передачи.
  4. Если значения ageMs малы, конфликты продолжают происходить — сохраните вывод RXSTAT json=1 и проанализируйте детализацию drop.details, чтобы найти типовые параметры.

После устранения причин можно вызвать rx.resetDropStats() и очистить историю fragmentMismatchHistory() через повторный вызов RXSTAT, чтобы убедиться в исчезновении новых записей.

Хранилище ключей и обмен

  • Ключи на ПК сохраняются в файле key_storage/key.stkey. Запись содержит две части: актуальный (current) и предыдущий (previous) набор данных. Это позволяет мгновенно вернуть старый ключ без доступа к дополнительным файлам.

  • В хостовых сборках приватный корневой ключ перед сохранением шифруется ключом устройства. По умолчанию используется предустановленный мастер-ключ и выводится предупреждение. Чтобы задать собственное значение, установите переменную окружения KEYLOADER_HOST_MASTER_KEY (64 HEX-символа, 32 байта).

  • На ESP32 по умолчанию используется NVS/Preferences. Раздел SPIFFS больше не применяется, поэтому требования к файловой системе для ключевого хранилища отсутствуют.

  • Перед обращением к NVS прошивка проверяет состояние Flash Encryption. При выключенной защите попытки чтения или записи завершаются ошибкой с уведомлением в Serial — необходимо включить шифрование флеша до использования KeyLoader.

  • Каждая запись включает корневую пару Curve25519, симметричный ключ AES-CCM, соль нонсов и последний публичный ключ удалённой стороны. При обновлении текущее состояние автоматически перемещается в секцию previous.

  • Для явного выбора хранилища доступна команда KEYSTORE (Serial/HTTP API):

    • KEYSTORE AUTO — автоматический выбор (по умолчанию).
    • KEYSTORE NVS — принудительное использование Preferences/NVS (ESP32).
    • KEYSTORE FS — использование локальной файловой системы (только в хостовых сборках).
    • KEYSTORE RETRY — повторная проверка и инициализация хранилища после устранения ошибок.
  • При ошибке подготовки хранилища устройство переходит в защищённый режим: шифрование отключается, команды ENC и все операции с ключами возвращают ошибку key-safe-mode, а ответы KEYSTATE и KEYSTORE показывают safeMode = true и ready = false. Дополнительно поле contextKEYSTORE) и safeModeContextKEYSTATE) содержат текстовую причину последней активации safe mode, чтобы было понятно, какая операция инициировала блокировку. После устранения причины выполните KEYSTORE RETRY (через Serial или HTTP), чтобы заново вызвать KeyLoader::ensureStorage() и снять блокировку.

  • Все команды управления ключами (KEYGEN, KEYGEN PEER, KEYRESTORE, KEYSEND/KEYTRANSFER SEND, KEYRECV/KEYTRANSFER RECEIVE) теперь перед выполнением автоматически пытаются повторно инициализировать хранилище. Если ошибка была временной (например, NVS успел подготовиться после старта), защита снимается без явного вызова KEYSTORE RETRY, и команда продолжит работу.

Генерация и передача ключей

  1. Вкладка Security показывает тип активного ключа (LOCAL или EXTERNAL), идентификатор (первые байты SHA-256 симметричного ключа), публичный корневой ключ, наличие резервной копии и используемое хранилище (storage/preferred в JSON ответах API).
  2. Кнопка KEYGEN генерирует новую пару Curve25519, обновляет симметричный ключ и сохраняет файл key.stkey, одновременно обновляя модули TxModule и RxModule. Веб-интерфейс корректно обрабатывает текстовые ответы (OK, KEYGEN DONE), даже если прошивка временно не возвращает JSON, и автоматически запрашивает актуальное состояние ключа. Из текстового ответа без JSON дополнительно извлекаются ID, PUB, PEER, наличие резервной копии и тип ключа, чтобы карточка состояния заполнилась сразу.
  3. Кнопка KEYGEN PEER пересчитывает симметричный ключ на основе сохранённого публичного ключа удалённого устройства (поле peer), повторно запуская процедуру ECDH. Кнопка активна только когда в состоянии есть непустое значение peer, что предотвращает случайные запросы без данных.
  4. KEY RESTORE возвращает key.stkey из key.stkey.old (если существует) и перечитывает ключи.
  5. KEYTRANSFER SEND формирует защищённый LoRa-кадр с публичным ключом устройства, шифрует его корневым AES-ключом и отправляет партнёру. В актуальном протоколе (версия 2) для каждого сеанса генерируется эпемерная пара X25519, публичный ключ включается в полезную нагрузку, а приватный ключ очищается сразу после обмена. Если удалённая сторона отвечает кадрами старого формата, прошивка фиксирует это и автоматически возвращается к совместимому кадру версии 1 без эпемерных полей. Веб-интерфейс дополнительно копирует ключ в буфер обмена.
  6. KEYTRANSFER RECEIVE запускает конечную автоматику ожидания кадра, сразу возвращает JSON {"status":"waiting"} и не блокирует основной цикл: radio.loop(), tx.loop(), flushPendingLogEntries() и maintainPushSessions() продолжают выполняться, поэтому SSE-клиенты продолжают получать логи. При получении кадра автоматика публикует результат в Serial на следующем проходе loop(), а HTTP-клиент может получить готовый JSON повторным запросом команды. В кадрах версии 2 общий секрет вычисляется из локального эпемерного приватного ключа и удалённого эпемерного публичного ключа; после завершения сеанса временные ключи затираются. При тайм-ауте команда возвращает {"error":"timeout"}.
  7. HKDF-SHA256 используется для вывода симметричных ключей и вспомогательной соли нонсов: локальная генерация (KEYGEN) использует публичный ключ устройства в качестве соли, а обмен с собеседником — отсортированную пару публичных ключей и разный info для статического/эпемерного режима. При загрузке хранилища старые записи автоматически мигрируются на новое вычисление nonce_salt.

Проверка идентификаторов KEYTRANSFER

  • После успешного применения публичного ключа прошивка вычисляет идентификатор активного сеансового ключа (KeyLoader::keyId(KeyLoader::loadKey())) и сравнивает его с remote_id, переданным в кадре. Для протокола версии 2 эта проверка пропускается: идентификатор можно вычислить только после завершения эпемерного обмена, а отправитель не знает локальный приватный ключ. В режиме совместимости (версия 1) несоответствие по-прежнему приводит к откату на предыдущий снимок (restorePreviousKey или локальный бэкап) и установке ошибки verify.
  • Лог (SimpleLogger) фиксирует сообщение вида KEYTRANSFER MISMATCH local/remote, а Serial выводит подробности и уведомляет об откате ключа. HTTP-команда /cmd?cmd=KEYTRANSFER&v=RECEIVE при первом вызове запускает ожидание и возвращает {"status":"waiting"}, при последующих запросах выдаёт готовый JSON с результатом (makeKeyStateJson() или {"error":...}), а Serial печатает тот же JSON автоматически при следующем проходе loop().
  • Состояние keyTransferRuntime после сбоя очищается, поэтому ожидание нового кадра можно повторить без перезапуска устройства: достаточно заново отправить команду KEYTRANSFER RECEIVE.

Дополнительно для хостовых сборок генератор crypto::x25519::random_bytes смешивает несколько источников энтропии: std::random_device (при доступности), /dev/urandom, метки времени и адреса стека. Это гарантирует уникальный seed между перезапусками даже при псевдослучайном поведении std::random_device.

Миграция со старого формата

  1. Если в NVS или в файле key_storage/key.stkey сохранена запись старого формата (один ключ + key.stkey.old), модуль KeyLoader автоматически загружает эти данные и перепаковывает их в новую структуру current/previous.
  2. Дополнительных действий не требуется: резервная копия будет перенесена в секцию previous, а вспомогательные ключи (key.stkey.old, backup) удалятся при следующей успешной записи.

Тест шифрования ENCT

Тестовая команда формирует короткое сообщение, шифрует его, расшифровывает обратно и выводит результат сравнения.

Запуск теста на ПК:

g++ -I. tests/test_enct.cpp libs_includes.cpp -std=c++17 && ./a.out

Через Serial (в src/main.cpp):

ENCT
ENCT: успех

Специальный корневой ключ

Обмен сообщениями KEYTRANSFER использует отдельный статический AES-ключ из libs/key_transfer. Он шифрует только LoRa-кадры с публичными ключами и не влияет на рабочий симметричный ключ трафика.

Сертификаты KEYTRANSFER

  • KeyTransfer::setTrustedRoot(const std::array<uint8_t,32>&) — сохраняет публичный корневой ключ удостоверяющего центра, относительно которого проверяется цепочка доверия. Корень хранится в памяти процесса и используется для всех входящих кадров.
  • KeyTransfer::setLocalCertificate(const CertificateBundle&) — регистрирует локальную цепочку сертификатов Ed25519, которая будет автоматически добавлена при формировании кадров через KeyTransfer::buildFrame().
  • KeyTransfer::verifyCertificateChain(const std::array<uint8_t,32>&, const CertificateBundle&, std::string*) — проверяет подписи Ed25519 в цепочке и подтверждает, что она восходит к доверенному корню.
  • Если доверенный корень не настроен (устройства используют одинаковую прошивку и общий стек), прошивка принимает кадр с сертификатом без проверки цепочки и фиксирует предупреждение в журнале, чтобы обмен ключами оставался совместимым.
  • KeyTransfer::hasTrustedRoot()/getTrustedRoot() и KeyTransfer::hasLocalCertificate()/getLocalCertificate() позволяют проверить наличие данных перед отправкой или обработкой кадра.

Для проверки подписей используется библиотека libsodium (crypto_sign_ed25519_verify_detached). В хостовых сборках необходимо установить пакет libsodium-dev и при сборке тестов добавлять флаг -lsodium:

g++ -I. tests/test_key_transfer.cpp \
    libs/key_transfer/key_transfer.cpp \
    libs/frame/frame_header.cpp \
    libs/scrambler/scrambler.cpp \
    libs/crypto/aes_ccm.cpp \
    libs/crypto/ed25519.cpp \
    libs/crypto/curve25519_donna.cpp \
    libs/crypto/x25519.cpp \
    libs/mbedtls/aes.c \
    libs/mbedtls/ccm.c \
    -lsodium -std=c++17 && ./a.out

Типы пакетов

Полный справочник вынесен в файл docs/packet_types.md. Ниже приведена краткая сводка основных форматов:

  • Пользовательские кадры — стандартные сообщения с заголовком FrameHeader, опциональным префиксом [TAG|i/n] и поддержкой шифрования/кодирования.
  • RAW/SP/GO — имена элементов ReceivedBuffer: сырые вставки R-000000|N, промежуточные SP-xxxxx и финальные GO-xxxxx.
  • ACK — подтверждение в виде однобайтового маркера 0x06.
  • Пинг — пятибайтовый тестовый пакет, который должен вернуться без изменений.
  • Маяк — служебный пакет BEACON с XOR-идентификатором.
  • KEYTRANSFER — защищённый кадр с публичным ключом для обмена корневой парой.
  • Legacy тест — исторический формат SendMsg_BR, используемый режимом TESTRXM для эмуляции входящих сообщений.

Справочник API

MessageBuffer

  • MessageBuffer(size_t capacity, size_t slot_size = 256) — создать буфер с ограничением на число сообщений и фиксированным размером слота под каждый фрагмент без повторного выделения памяти.
  • Конструктор автоматически исправляет нулевые параметры (вместимость и размер слота) до минимально допустимого значения 1, чтобы избежать деления по модулю нулевой ёмкости и пустых сообщений.
  • uint32_t enqueue(const uint8_t* data, size_t len) — добавить сообщение, при переполнении вернуть 0.
  • size_t freeSlots() const — количество свободных слотов.
  • bool dropLast() — удалить последнее сообщение (откат).
  • bool hasPending() const — наличие сообщений.
  • bool pop(uint32_t& id, std::vector<uint8_t>& out) — извлечь сообщение и его идентификатор.
  • const std::vector<uint8_t>* peek(uint32_t& id) const — указатель на данные первого сообщения без извлечения.
  • size_t slotSize() const — узнать, какой объём данных допустим в одном слоте очереди.

ReceivedBuffer

  • std::string pushRaw(uint32_t id, uint32_t part, const uint8_t* data, size_t len) — сохранить сырой пакет и получить имя R-000000|номер.
  • std::string pushSplit(uint32_t id, const uint8_t* data, size_t len) — сохранить объединённые данные SP-00000.
  • std::string pushReady(uint32_t id, const uint8_t* data, size_t len) — сохранить готовые данные GO-00000.
  • Item::name хранит заранее подготовленную подпись, повторные обращения не требуют форматирования.
  • bool popRaw(Item& out), bool popSplit(Item& out), bool popReady(Item& out) — извлечь данные.
  • std::vector<std::string> list(size_t count) — имена последних элементов (не более count), начиная с самых свежих.
  • std::vector<SnapshotEntry> snapshot(size_t count) — копия последних элементов с типом очереди для формирования JSON-ответа RSTS.
  • void setNotificationCallback(NotificationCallback cb) — назначить обработчик, который вызывается при каждом pushRaw/pushSplit/pushReady.
  • При активном RxModule::setBuffer() короткие и повреждённые кадры без заголовка сохраняются как RAW-пакеты с идентификатором 0 и увеличивающимся part, чтобы не терять последовательность одиночных байтов и не смешивать их с обычными сообщениями.

PacketSplitter

  • PacketSplitter(PayloadMode mode, size_t custom = 0) — создать делитель с нужным режимом или произвольным размером блока.
  • void setMode(PayloadMode mode) и void setCustomSize(size_t custom) — сменить режим и размер.
  • uint16_t splitAndEnqueue(MessageBuffer& buf, const uint8_t* data, size_t len) — разбить данные на части с проверкой свободных слотов и откатом при ошибке.
  • Для многочастных сообщений в начало каждого сегмента добавляется префикс [ABCDE|1/5]: общее имя, номер текущей части и общее количество фрагментов. Идентификатор ABCDE теперь выдаётся монотонным счётчиком и сериализуется восьмизначным шестнадцатеричным числом ([0000ABCD|1/5]), что гарантирует уникальность в рамках сессии. Получатель использует эти пометки, чтобы заново собрать изображение или длинный текст в исходном порядке.
  • Дополнительно поле frag_cnt в заголовке кадра передаёт точное количество фрагментов: как только принимающая сторона увидит часть с индексом frag_idx = frag_cnt - 1, PacketGatherer понимает, что сообщение собрано полностью и можно отдавать полезную нагрузку дальше.

PacketGatherer

  • PacketGatherer(PayloadMode mode, size_t custom = 0) — создать собиратель.
  • void reset() — очистить состояние.
  • void add(const uint8_t* data, size_t len) — добавить часть сообщения.
  • bool isComplete() const — проверить завершение сбора (актуально при фиксированном размере блока).
  • const std::vector<uint8_t>& get() const — получить готовое сообщение.
  • Сборщик автоматически подстраивает базовый размер блока по первому фрагменту и сбрасывает накопленное, если приходит новый msg_id — так исключается смешивание незавершённых сообщений разных пакетов.

FrameHeader

  • bool encode(uint8_t* out, size_t out_len, const uint8_t* payload, size_t payload_len) — записать заголовок в буфер по компактной схеме без вычисления CRC; длина полезной нагрузки упаковывается в поле packed и дополнительно передаётся параметром.
  • static bool decode(const uint8_t* data, size_t len, FrameHeader& out) — разобрать заголовок в формате версия | msg_id | frag_cnt | packed.
  • uint8_t getFlags() const / void setFlags(uint8_t) — доступ к флагам, упакованным в восемь старших бит packed.
  • uint16_t getFragIdx() const / void setFragIdx(uint16_t) — получить или задать индекс фрагмента, занимающий 12 средних бит packed.
  • uint16_t getPayloadLen() const / void setPayloadLen(uint16_t) — получить или задать длину полезной нагрузки (12 младших бит packed).
  • Флаги FLAG_CONV_ENCODED, FLAG_RS_ENCODED и FLAG_BIT_INTERLEAVED отражают реальное применение свёртки, кода Рида—Соломона и битового интерливинга. При смене настроек на лету заголовок всегда сообщает фактический режим кодирования, поэтому приёмник может автоматически адаптироваться.
  • Заголовок занимает 12 байт: байт версии, по два байта для msg_id и frag_cnt, четыре байта packed и три зарезервированных нуля для выравнивания тега AEAD.
  • Контроль целостности обеспечивается AEAD-тегом; поля ack_mask, hdr_crc и frame_crc удалены, а crc16() оставлена как вспомогательный инструмент для проверки пилотов и исторических форматов.

TxModule

  • TxModule(IRadio& radio, const std::array<size_t,4>& capacities, PayloadMode mode) — инициализация с четырьмя очередями QoS.
  • void setPayloadMode(PayloadMode mode) — сменить размер пакета.
  • uint16_t queue(const uint8_t* data, size_t len, uint8_t qos = 0) — добавить сообщение в очередь.
  • bool loop() — попытаться отправить один кадр (фрагмент) очередного сообщения. Возвращает true, если кадр ушёл в эфир, и false, когда требуется дождаться паузы/ACK или очередь пуста. Для передачи длинного сообщения требуется несколько последовательных вызовов loop().
  • Прогресс по каждому сообщению (индекс фрагмента и таймер паузы) хранится внутри очереди, поэтому можно без блокировок продолжать передачу после ожидания ACK или внешних операций.
  • void setSendPause(uint32_t pause_ms) и uint32_t getSendPause() const — пауза между отправками.
  • void setAckTimeout(uint32_t timeout_ms) и uint32_t getAckTimeout() const — управление тайм-аутом ожидания ACK. Значение 0 отключает ожидание подтверждений: флаг waiting_ack_ сразу сбрасывается, пакет помечается доставленным и очередь продолжает обработку следующих элементов без паузы.
  • void setAckResponseDelay(uint32_t delay_ms) — задержка перед отправкой ACK после успешного приёма; значение 0 отключает дополнительную паузу и подтверждение уходит сразу. Параметр доступен через команды ACKD (Serial/HTTP) и одноимённое поле на вкладке Settings.
  • Подтверждение (ACK) формируется отдельной веткой как минимальный кадр FrameHeader без дублей и CRC с полезной нагрузкой 0x06. Шифрование и свёрточное кодирование пропускаются, поэтому флаги FLAG_ENCRYPTED/FLAG_CONV_ENCODED остаются сброшенными.
  • Подробное описание формата и особенностей обработки подтверждения приведено в разделе «Структура ACK-пакета без дополнительного кодирования».
  • void prepareExternalSend() / void completeExternalSend() — учёт глобальной паузы при внешней отправке через RadioSX1262.
  • При ожидании подтверждения радиоинтерфейс переводится в режим приёма; пауза применяется между частями одного сообщения.
  • Последовательность кодирования: rs255223::encode()byte_interleaver::interleave()conv_codec::encodeBits() → (опционально) bit_interleaver::interleave()scrambler::scramble().
  • static void setEncryptOverrideForTests(TxModule::EncryptOverride fn) и static void resetEncryptOverrideForTests() — тестовый хук для подмены реализации ChaCha20-Poly1305. Используется только в src/tests, чтобы эмулировать ошибки подготовки фрагментов без переписывания производственного кода.

RxModule

  • void setCallback(RxModule::Callback cb) — обработчик входящих данных.
  • void setAckCallback(std::function<void()> cb) — уведомление о поступившем ACK; удобно привязывать к TxModule::onAckReceived() или собственной логике подтверждений.
  • void onReceive(const uint8_t* data, size_t len) — принять кадр, проверить CRC и передать данные; слишком короткие или повреждённые фреймы без валидного заголовка направляются напрямую в пользовательский колбэк (и в ReceivedBuffer при его наличии) без дескремблирования.
  • void enableProfiling(bool enable) — включает накопление телеметрии по этапам обработки кадра; вместе с lastProfiling() позволяет контролировать задержки и точки отбраковки.
  • RxModule::ProfilingSnapshot lastProfiling() const — возвращает последний снимок: длительность этапов (descramble, header, payload_extract, decode, decrypt, assemble, deliver), флаги использования свёртки/дешифрования и причину досрочного выхода (если кадр не был собран).
  • RxModule::DropStats dropStats() const — агрегированная статистика причин отбраковки кадров; полезно для анализа деградации канала при низком SNR и повторяющихся ошибок. Для каждой причины дополнительно накапливаются уникальные параметры (например, expected, got, offset), а также несколько последних примеров для оперативного разбора инцидентов без покадровых логов.
  • std::vector<RxModule::FragmentMismatchInfo> fragmentMismatchHistory() const — кольцевой буфер последних конфликтов индексов фрагментов: возвращает идентификатор сообщения, ожидаемый и фактический индексы, объявленное frag_cnt и age_ms, показывающий давность события. Эти данные помогают быстро понять, нарушена ли последовательность доставки или очередь QoS.
  • void resetDropStats() — сброс накопленной статистики дропа, чтобы оценить свежую картину после изменения параметров приёма.
  • void setBuffer(ReceivedBuffer* buf) — привязать буфер для автоматического сохранения сообщений.
  • void tickCleanup() — запустить таймерную очистку кэшей свёрточных блоков и незавершённых split вручную; полезно вызывать из главного цикла даже при отсутствии новых кадров.
  • Буферы кадра, полезной нагрузки и результата переиспользуются между вызовами.
  • Обратная цепочка обработки: scrambler::descramble() → (опционально) bit_interleaver::deinterleave()conv_codec::viterbiDecode()byte_interleaver::deinterleave()rs255223::decode().
  • Префиксы вида [TAG|n/m] используются для ленточного объединения: RxModule накапливает сегменты в промежуточном буфере pending_split_, фиксирует прогресс через pushSplit, а готовый результат (n == m) выдаёт одним вызовом колбэка и сохраняет в ReceivedBuffer.
  • Модуль отслеживает msg_id и ожидаемые индексы фрагментов, сбрасывая сборку при разрыве последовательности — неполные сообщения больше не смешиваются с новыми кадрами.
  • Кэш свёрточных блоков автоматически очищается по таймеру; одновременно pending_conv_ и pending_split_ ограничиваются 64 свежими элементами — самые старые записи удаляются, чтобы не накапливать лишнюю память.
  • Для временной диагностики RxModule сверяет hdr.getPayloadLen() с собственными расчётами длины свёртки и выводит подробные логи по каждому ключу pending_conv_: объём пришедшего фрагмента, накопленный объём, ожидаемую длину и общий размер всех незавершённых свёрточных буферов.
  • Принудительное шифрование теперь не блокирует приём открытых кадров: при ошибке AES-CCM фрагмент обрабатывается как незашифрованный с предупреждением в лог.
  • Детальный разбор этапов и типовых мест задержек приведён в docs/rx_pipeline_profiling.md.

Режим приёма сырых пакетов

Слишком короткие кадры (< 40 байт) и пакеты, в которых не удалось восстановить заголовок, теперь возвращаются напрямую в пользовательский колбэк без дескремблирования и последующей проверки. Это позволяет не терять одиночные байты от старых клиентов или сервисных каналов. При активном ReceivedBuffer такие входящие попадают в очередь RAW со специальным идентификатором 0 и монотонным part, что отделяет их от нормальных сообщений. Ограничения режима:

  • данные не дешифруются и не проходят проверку CRC — ответственность на потребителе;
  • msg_id отсутствует, поэтому привязка к собранным сообщениям невозможна;
  • пакеты не участвуют в сборке PacketGatherer, не образуются элементы SP- и GO-.

Клиенты, которые анализируют вывод RSTS, должны фильтровать записи R-000000|N и относиться к ним как к самостоятельным сырым пакетам.

RadioSX1262

  • bool begin() — инициализация радиомодуля с возвратом параметров к значениям по умолчанию.
  • void send(const uint8_t* data, size_t len) — отправка пакета.
  • bool ping(...) — пинг с ожиданием эха и возвратом параметров приёма.
  • void loop() и void setReceiveCallback(...) — обработка готовности пакета и регистрация колбэка.
  • float getLastSnr() const, float getLastRssi() const — параметры последнего пакета.
  • uint8_t randomByte() — случайный байт от радиомодуля.
  • Настройка и получение параметров: setBank, setChannel, setBandwidth, setSpreadingFactor, setCodingRate, setPower, getBank, getChannel, getBankSize, getBandwidth, getSpreadingFactor, getCodingRate, getPower, getRxFrequency, getTxFrequency.
  • bool setRxBoostedGainMode(bool enabled), bool isRxBoostedGainEnabled() const — управление режимом повышенного усиления приёмника.
  • static uint16_t bankSize(ChannelBank bank), static float bankRx(...), static float bankTx(...), static const char* bankDescription(...) — справочные методы.
  • static void logIrqFlags(uint32_t flags) — форматированный вывод активных IRQ-флагов радиочипа в журнал через DEBUG_LOG, чтобы сообщения не поднимали общий уровень логирования. При наличии в RadioLib отдельных масок RADIOLIB_IRQ_RX_TIMEOUT и RADIOLIB_IRQ_TX_TIMEOUT вывод различает таймауты приёма и передачи для ускоренной диагностики.
  • При успешной инициализации begin() один раз выводит отладочное сообщение IRQ logger started, подтверждая запуск механизма логирования IRQ.
  • void flushPendingIrqLog() — обходной ручной вызов переноса и вывода отложенных IRQ-логов.
  • bool resetToDefaults() — возврат параметров к значениям по умолчанию.
  • Вспомогательная обёртка PublicSX1262 добавляет совместимые методы getIrqFlags(), clearIrqFlags(...) и getIrqStatus(uint16_t*), чтобы прошивка одинаково собиралась с разными версиями RadioLib.
  • После применения настроек по умолчанию LoRa-модем переводится в режим implicit header с фиксированной длиной полезной нагрузки 64 байта, преамбулой из 16 символов, а send() автоматически дополняет более короткие кадры нулями. ping() временно включает explicit header, чтобы передавать сервисный пакет без привязки к длине данных.
  • loop() блокирует доступ к радиочипу через общий мьютекс и пропускает чтение, если модуль занят внешней операцией (send/ping), что исключает гонку и случайные перезагрузки при одновременном доступе из разных задач.

SerialProgramCollector

  • void resetBuffer() — очистить буфер.
  • bool appendToBuffer(const String& line) — добавить строку с проверкой переполнения.

src/main.cpp

  • String makeAccessPointSsid() — формирует имя точки доступа с коротким суффиксом из уникального идентификатора устройства.
  • void resetKeyTransferWaitTiming() — сбрасывает временные отметки ожидания KEYTRANSFER, чтобы повторный запрос начинался с корректных значений дедлайна после успеха, тайм-аута или ошибки.
  • void configureKeySafeModeController(std::function<void(bool)> applyEncryption, bool* encryptionFlag, bool* safeModeFlag, bool* storageReadyFlag) — связывает логику safe mode с глобальными флагами и колбэком шифрования, сохраняет исходное encryptionEnabled, отключает шифрование при недоступности хранилища и гарантированно восстанавливает режим либо фиксирует ошибку в журнале при неудаче.
  • bool keySafeModeHasReason() — сообщает, есть ли сохранённый контекст причины включения защищённого режима.
  • std::string keySafeModeReason() — возвращает текстовый контекст последней активации safe mode (пустая строка, если причина не зафиксирована).

TextConverter

  • std::vector<uint8_t> utf8ToCp1251(const std::string& in) — UTF-8 → CP1251.
  • std::string cp1251ToUtf8(const std::vector<uint8_t>& data) — CP1251 → UTF-8 (неизвестные символы заменяются на ?).

rs255223

  • void encode(const uint8_t* in, uint8_t* out) — кодирует 223 байта в 255.
  • bool decode(const uint8_t* in, uint8_t* out) — декодирует 255 байт и возвращает 223 байта.

conv_codec

  • void encodeBits(const uint8_t* in, size_t len, std::vector<uint8_t>& out) — свёрточное кодирование (R=1/2).
  • bool viterbiDecode(const uint8_t* in, size_t len, std::vector<uint8_t>& out) — декодирование алгоритмом Витерби.

bit_interleaver

  • void interleave(uint8_t* buf, size_t len) — битовый интерливинг.
  • void deinterleave(uint8_t* buf, size_t len) — обратный интерливинг.

KeyLoader

  • std::array<uint8_t,16> loadKey() — загрузить активный симметричный ключ (при отсутствии создаётся запись по умолчанию).
  • bool saveKey(const std::array<uint8_t,16>& key, KeyOrigin origin, const std::array<uint8_t,32>* peer, uint32_t salt) — сохранить ключ с указанием происхождения и публичного ключа собеседника.
  • bool generateLocalKey(KeyRecord* out = nullptr) — создать новую пару Curve25519, обновить симметричный ключ и переместить предыдущую запись в секцию previous (для последующего восстановления). Сеансовый ключ и nonce_salt выводятся через HKDF-SHA256 с солью из публичного ключа устройства.
  • bool restorePreviousKey(KeyRecord* out = nullptr) — восстановить key.stkey из резервной копии.
  • bool applyRemotePublic(const std::array<uint8_t,32>& remote, const std::array<uint8_t,32>* remote_ephemeral = nullptr) — вычислить общий секрет и сохранить внешний симметричный ключ. При передаче эпемерного ключа обмен выполняется в рамках протокола версии 2 и использует временную пару X25519, которая очищается после успешного (или аварийного) завершения. Соль HKDF формируется из отсортированной пары публичных ключей, а info различается для статического и эпемерного режимов.
  • bool regenerateFromPeer(const std::array<uint8_t,32>* remote_ephemeral = nullptr, KeyRecord* out = nullptr) — повторно применить сохранённый публичный ключ удалённой стороны; при наличии эпемерного ключа пересчёт выполняется с учётом временной пары.
  • bool hasPeerPublic() — проверить, сохранён ли публичный ключ удалённого собеседника.
  • bool previewPeerKeyId(std::array<uint8_t,4>& key_id) — пересчитать идентификатор ECDH-ключа для текущего партнёра без изменения снимка.
  • KeyState getState() — текущее состояние ключа (тип, идентификатор, публичные ключи, резервная копия).
  • std::array<uint8_t,12> makeNonce(uint8_t version, uint16_t frag_cnt, uint32_t packed_meta, uint16_t msg_id) — сформировать нонс для AEAD (ChaCha20-Poly1305 и совместимого режима AES-CCM).
  • bool startEphemeralSession(std::array<uint8_t,32>& public_out, bool force_new = true) — подготовить эпемерную пару X25519 и вернуть публичный ключ для включения в кадр обмена.
  • bool hasEphemeralSession() — проверить, активна ли временная пара для текущего сеанса.
  • void endEphemeralSession() — стереть эпемерный приватный ключ и сбросить состояние обмена.
  • void setLogCallback(KeyLoader::LogCallback callback) — передать обработчик логов (например, после Serial.begin()), чтобы накопленные сообщения KeyLoader выгрузились безопасно и не обращались к неинициализированному UART; обработчик должен возвращать true при успешной доставке сообщения и false, если попытку требуется повторить позже (например, до появления USB). Тип KeyLoader::LogCallback на Arduino получает const __FlashStringHelper*, чтобы не копировать строки из PROGMEM, а в хостовых сборках принимает const char*.
  • void flushBufferedLogs() — вручную инициировать повторную попытку выгрузки буфера KeyLoader, например, сразу после того как Serial стал доступен без перепривязки обработчика.

crypto/hkdf

  • Prk extract(const uint8_t* salt, size_t salt_len, const uint8_t* ikm, size_t ikm_len) — стадия HKDF-Extract (HMAC-SHA-256 по соли и входному материалу).
  • bool expand(const Prk& prk, const uint8_t* info, size_t info_len, uint8_t* okm, size_t okm_len) — стадия HKDF-Expand с выводом заданного количества байтов.
  • bool derive(...) — обёртка, последовательно выполняющая Extract и Expand; доступна версия для указателей и шаблонная обёртка для стандартных контейнеров.

ChaCha20-Poly1305

  • bool crypto::chacha20poly1305::encrypt(...) — шифрование с формированием тега Poly1305 и поддержкой дополнительного связанных данных.
  • bool crypto::chacha20poly1305::decrypt(...) — проверка тега Poly1305 и расшифровка.

AES-CCM (совместимость)

  • bool encrypt_ccm(...) — легаси-шифрование для кадров версии 1.
  • bool decrypt_ccm(...) — расшифровка и проверка легаси-тегов.

Файл конфигурации

  • Основные параметры запуска выносятся в файл config/default.ini. Он использует простой формат INI и делится на секции [wifi], [radio] и [keys].
  • При старте прошивка читает файл через модуль libs/config_loader/; если файл отсутствует либо содержит ошибки, в лог выводится предупреждение, а работа продолжается со значениями из DefaultSettings.
  • Параметры, непосредственно влияющие на инициализацию RadioLib/SX1262 (кроме выбора частот, полосы, SF, CR и мощности), сведены в src/libs/radio/lora_radiolib_settings.h. В файле перечислены все управляемые опции драйвера с русскоязычными комментариями и дефолтными значениями.
  • Пример содержимого:
    [wifi]
    ssid=sat_ap
    password=12345678
    ip=192.168.4.1
    gateway=192.168.4.1
    subnet=255.255.255.0
    
    [radio]
    bank=TEST
    channel=0
    powerPreset=0
    bwPreset=2
    sfPreset=2
    crPreset=0
    rxBoostedGain=true
    useAck=false
    ackRetryLimit=3
    ackResponseDelayMs=20
    sendPauseMs=370
    ackTimeoutMs=320
    useEncryption=true
    useRs=false
    useConv=true
    useBitInterleaver=true
    
    [keys]
    default=000102030405060708090a0b0c0d0e0f
  • Для изменения параметров достаточно отредактировать соответствующие ключи и перезапустить устройство. В хостовой сборке конфигурацию можно перечитать без перезапуска, вызвав ConfigLoader::reload().
  • Настройки useConv и useBitInterleaver позволяют временно отключить свёрточное кодирование и битовый интерливинг для отладки. При выключении useConv RS-блоки также не формируются, а заголовок кадра автоматически сообщает фактический режим кодирования.
  • Все строки допускают комментарии, начинающиеся с ; или #, и игнорируют лишние пробелы. Некорректные значения фиксируются в логе (Config: некорректный …) без остановки прошивки.

Wi-Fi точка доступа

  • Имя сети формируется автоматически: к базовому префиксу из конфигурации добавляется шестнадцатеричный суффикс из последних трёх байтов MAC-адреса, поэтому итоговый SSID выглядит как sat_ap-12ABCD.
  • Прошивка запускает точку доступа в диапазоне 2,4 ГГц и фиксирует IP-адрес из конфигурации (по умолчанию 192.168.4.1). При сбоях конфигурации используется штатная сетка ESP32/ESP8266, что видно в Serial-журнале по предупреждению «Wi-Fi: не удалось применить статический IP…».
  • Добавлена автоматическая повторная инициализация: если запуск точки доступа завершился неудачей, модуль выполняет до трёх попыток и выводит сообщение «Wi-Fi: повторный запуск точки доступа (попытка N)». После успешного старта в журнале появляется строка «Wi-Fi: точка доступа … запущена, IP …».
  • Перед настройкой сети вызывается функция waitForSerial(timeout_ms), которая ждёт появления USB-Serial не дольше заданного интервала (1,5 с по умолчанию) и не позволяет зависнуть старту точки доступа при отключённом ПК.
  • Если сеть по-прежнему не видна, проверьте питание, антенну и наличие сообщений об ошибках в Serial-консоли. При необходимости можно перезапустить устройство: повторные попытки очистят сохранённые STA-параметры и принудительно вернут модуль в режим точки доступа.

Команды Serial

  • BANK <e|w|t|a|h|n> — выбрать банк каналов.
  • CH <номер> — переключить канал в текущем банке.
  • PI — отправить пинг, ожидать эхо PING_WAIT_MS миллисекунд и вывести RSSI/SNR либо timeout.
  • SEAR — последовательный пинг всех каналов с выводом RSSI/SNR и отметкой времени ожидания.
  • ACK [0|1] — включить/выключить подтверждения.
  • LIGHT [0|1] — включить или выключить режим прямой отправки текста (Light pack) с raw-пакетом напрямую в SX1262.
  • RXBG <0|1|toggle> — переключить режим повышенного усиления приёмника SX1262.
  • KEYTRANSFER SEND — отправить публичный корневой ключ по защищённому каналу.
  • KEYTRANSFER RECEIVE — ожидать защищённый кадр и применить ключ при успехе.
  • Дополнительно доступны команды BF, SF, CR, PW, RXBG, TX, TXL, BCN, INFO, STS <n>, RSTS <n> (список имён) / RSTS FULL <n> (RSTS JSON <n> для вывода JSON с данными), ACKR <повторы>, PAUSE <мс>, ACKT <мс>, ACKD <мс>, ENC [0|1], LIGHT, TESTRXM.
  • Значение ACKT 0 выключает ожидание подтверждений: текущее сообщение считается доставленным, очередь не блокируется и немедленно берёт следующий пакет.
  • Команда ACKD задаёт задержку ответа на входящий пакет (0–5000 мс) до постановки ACK в очередь.

Дополнительно введён банк HOME (буква h/H) — это подборка из семи каналов полного банка ALL. Банк NEW (буква n/N) содержит все новые пары частот из каналов 163–308 и позволяет работать с обновлённым списком без переключения на полный банк ALL. Значения частот для быстрого доступа:

  • RX: 262.425, 257.500, 257.150, 255.775, 267.200, 249.930, 245.950 МГц.
  • TX: 296.025, 311.350, 298.150, 309.300, 308.200, 308.750, 299.400 МГц.

Пример последовательности обработки

// Передача
шифрование -> PacketSplitter (223 байта) -> rs255223::encode ->
byte_interleaver::interleave -> conv_codec::encodeBits ->
bit_interleaver::interleave -> scrambler::scramble -> отправка

// Приём
scrambler::descramble -> bit_interleaver::deinterleave ->
conv_codec::viterbiDecode -> byte_interleaver::deinterleave ->
rs255223::decode -> PacketGatherer -> обработка сообщения

Тестирование

Внешние зависимости тестов

  • Криптографические проверки (tests/test_key_transfer.cpp, libs/crypto/*.cpp) используют функциональность libsodium. Убедитесь, что установлен development-пакет библиотеки (для Debian/Ubuntu: sudo apt-get install libsodium-dev).

Сборка и запуск тестов

  • Для сборки бинарника tests/test_key_transfer.cpp вместе с криптомодулями можно воспользоваться tests/Makefile:
    make -C tests
    ./tests/build/test_key_transfer
    При необходимости можно указать другой набор исходников, передав TEST_SRCS="test_key_transfer.cpp test_enct.cpp".
  • Для проверки форматирования IRQ-логов радиомодуля подготовлен отдельный тест:
    make -C tests build/test_radio_irq_logging
    ./tests/build/test_radio_irq_logging
    Заглушки для Arduino/RadioLib подключаются автоматически, поэтому аппаратное окружение не требуется.
  • Комплексный тест полного цикла без радиоканала по-прежнему можно собрать напрямую:
    g++ -I. -I.. tests/test_processing_without_send.cpp tx_module.cpp rx_module.cpp \
        message_buffer.cpp libs_includes.cpp -std=c++17 && ./a.out
  • В процессе теста проверяется, что короткие сообщения проходят напрямую в колбэк и попадают в RAW-очередь ReceivedBuffer, если она активирована.
  • Проверка логики веб-интерфейса выполняется отдельным набором модульных тестов на Node.js (используется встроенный node:test):
    node --test tests/web_ui/web_ui.test.js
    Перед запуском убедитесь, что установлен Node.js версии 18+ (в контейнере используется v22.19).
  • Базовые тесты буфера сообщений, делителя пакетов и формирования кадров находятся в каталоге tests/.
  • Тест совместимости RxModule с внешним ReceivedBuffer подтверждает корректное отображение готовых сообщений.
  • Тест tests/test_pilot_marker.cpp проверяет, что новый пилотный маркер не удаляет полезные данные, даже если нагрузка содержит «старую» двухбайтовую сигнатуру.

Что реализовано

  • Прямая передача данных без изменений: TxModule ставит сообщения в очередь и отправляет их в LoRa без заголовков, шифрования и ФЕК.
  • Простая доставка входящих кадров: RxModule без дополнительных проверок пробрасывает полезную нагрузку в пользовательский колбэк.
  • Базовые очереди сообщений и вспомогательные буферы продолжают работать как оболочка над сырым потоком, сохраняя идентификаторы и минимальный контроль переполнения.
  • Вся прежняя логика обработки сохранена в исходниках и может быть возвращена по мере необходимости.

Что осталось сделать

  • Вернуть ACK, ретраи и планирование повторов, если потребуется подтверждение доставки.
  • Реактивировать шифрование ChaCha20-Poly1305/AES-CCM и управление ключами на уровне конвейера.
  • Включить обратно RS-код, интерливинг, свёрточное кодирование и работу с пилотами.
  • Снова собрать пайплайн PacketSplitter/PacketGatherer, чтобы поддерживать большие сообщения и управление префиксами.
  • Обновить тесты и документацию после включения каждой стадии, чтобы отразить переход от прямого режима к полноценному.

Исторический бэклог

  • Радио и планирование
    • Расширить управление радиомодулем дополнительными командами и параметрами.
    • Внедрить планировщик WFQ для классов QoS.
  • Наведение антенны
    • Автоматически обновлять TLE и напоминать пользователю о необходимости актуализации данных.
    • Расширить поддержку MGRS (точность ниже 100 км, корректная работа в южном полушарии и проверка некорректных квадратов).
    • Добавить учёт горизонта и препятствий (маски высоты) и визуальную подсказку с картой линии визирования.
  • Обработка сигнала
    • Реализовать обработку пилотов в RxPipeline (updatePhaseFromPilot) вместо простого удаления.
    • Доработать алгоритмы коррекции ошибок и добавить дополнительные схемы шифрования при необходимости.
    • Добавить расширенные метаданные для сырых пакетов (например, отметки времени или источник), чтобы упростить автоматический анализ потока.
  • Текст и совместимость
    • Добавить поддержку дополнительных кодировок и символов в TextConverter.
    • Сохранение собранной программы и расширенную обработку команд завершения в SerialProgramCollector.
  • Тесты и верификация
    • Расширить тесты обработки пилотов (базовая проверка отсутствия потерь данных добавлена в tests/test_pilot_marker.cpp).
    • Добавить отдельные тесты/настройки для битового интерливинга и свёрточного кодека.
  • Обмен изображениями
    • Добавить автоматические проверки обработки изображений в браузере и на прошивке.
    • Расширить контроль очистки кэша и параметров профилей (HEIC/EXIF) после полевых испытаний.
  • Конфигурация
    • Добавить редактирование config/default.ini через веб-интерфейс и синхронизацию изменений без перезагрузки устройства.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published