Compare commits

..

196 Commits

Author SHA1 Message Date
483c174cd5 обновление TDMA вебки: добавил лимит ослабления 2025-09-09 13:56:16 +03:00
eb1e4609bb обновление ШПС вебки: исправил порог коррелятора по просьбе @hernayo, обновление control system api 2025-09-08 11:27:35 +03:00
00a9692b3d обновление ШПС вебки: исправил порог коррелятора по просьбе @hernayo 2025-09-06 12:12:09 +03:00
89004bcf31 обновление ШПС вебки: исправление полей для ввода частоты 2025-09-05 17:48:35 +03:00
b580ac6b37 обновление ШПС вебки: добавление новых полей, удаление полей из мониторинга и обновление страницы настроек 2025-09-05 16:09:37 +03:00
7cd096a269 feature: добавил поле для ввода пароля входа в сеть ЦЗС 2025-08-13 11:16:14 +03:00
beca00ff70 fix: восстановил чтение статуса обновление с предупреждением, что в этом месте API зависает 2025-07-18 16:32:05 +03:00
ed6694b99d CI: установка переменной GIT_SUBMODULE_STRATEGY=recursive 2025-07-14 13:31:46 +03:00
305a8c54fa Merge remote-tracking branch 'origin/tdma-lo-feature' 2025-07-14 11:38:43 +03:00
297e681b75 привет, зоопарк веток control_system_client! зато теперь вебка должна собираться для всех модемов из одной ветки 2025-07-14 11:37:40 +03:00
32720ca365 fix: scpc некорректный максимальный модкод для ACM 2025-07-10 11:26:42 +03:00
33bbdafc3d fix: tdma временное отключение мониторинга статуса обновления и фикс установки настроек DPDI 2025-07-05 15:10:09 +03:00
bfcdc88da9 fix: tdma+scpc не читались и не применялись настройки DPDI 2025-07-05 14:38:04 +03:00
760a6858ee feature: LO freq для BUC и LNB 2025-07-05 13:54:21 +03:00
e9ff1d099a fix: set ACM settings 2025-07-02 15:33:06 +03:00
11e4561551 fix: aupcRequiredSnr - изменена граница допустимых значений 2025-07-02 14:52:17 +03:00
081ef1b84f fix: большие числа в мониторинге параметров (2) 2025-07-02 13:20:30 +03:00
bb90a3fec2 unlock: установил нижний предел частот 100MHz вместо 950MHz 2025-06-17 13:08:00 +03:00
9fbe88b64d фича: маска для сети управления + исправление багов на странице для разработчиков 2025-06-05 17:07:27 +03:00
996e711436 фича: бекап параметров и восстановление через вебку 2025-06-05 15:21:18 +03:00
57ba61da41 фикс огромных чисел на странице мониторинга 2025-06-05 11:22:34 +03:00
b9a25e8734 фикс roll-off, фикс нечитающихся настроек сети, фикс значений на странице мониторинга, добавил авто-регулировку мощности в ШПС 2025-06-05 11:15:13 +03:00
1e185a987d мелкие косметические изменения, добавил в вебку ШПС настройки сети 2025-06-05 10:29:58 +03:00
4c555d5400 bugfix: исправление ошибки, из-за которой не показываются сведения о ПО и системе 2025-06-04 16:54:40 +03:00
38a00173a2 отладочная версия веб-сервера, работает со статистикой и настройкой. Не показывает параметры прошивки 2025-06-04 13:23:27 +03:00
e5e6878351 компилируемая версия веб-сервера, находится на стадии отладки 2025-06-04 11:04:51 +03:00
55fc322c13 исправления методов-заглушек, часть 1 2025-06-03 18:32:28 +03:00
f30e1adb49 компилируемая версия для всех версий модемов, пока без CP Proxy 2025-06-03 16:16:41 +03:00
50f82483fd исправления для TDMA 2025-06-01 15:59:42 +03:00
c2dd4b13e8 условно компилируемый проект после рефактора, еще нет настроек rxtx 2025-06-01 15:49:31 +03:00
3e38e77069 какая-то куча изменений (2). тут уже вырисовывается новая архитектура бекенда для cp api 2025-05-29 18:56:19 +03:00
3745f98a82 какая-то куча изменений 2025-05-27 10:55:50 +03:00
17cdd69207 фикс: бесконечное обновление кеша из-за ошибки в логике цикла обновления 2025-05-06 14:37:19 +03:00
9444fc7ebc SCPC: поменял надпись на русскую 2025-05-05 18:27:24 +03:00
ec8cb40aea SCPC: поменял надпись на русскую 2025-05-05 18:26:59 +03:00
53b4e65588 CI updates 2025-05-05 18:18:37 +03:00
f4b6e194f2 пофиксил структуру для control system client (2) 2025-05-05 18:13:01 +03:00
8c49af0d6e пофиксил структуру для control system client 2025-05-05 18:01:56 +03:00
32983e97b3 добавление настроек сети для TDMA-терминала 2025-04-23 15:05:04 +03:00
be533d7521 фикс: питание LNB не сбрасывалось 2025-04-22 13:07:48 +03:00
35b25287ad добавление пилотов в вебку 2025-04-22 11:55:06 +03:00
008ad2f184 CI: исправления пайплайна 2025-04-22 10:38:24 +03:00
bd8909832f CI: тестирование сборки всех конфигураций проекта 2025-04-21 17:35:33 +03:00
4d1462211c Merge branch 'refs/heads/dev-log-chart' 2025-04-08 17:37:41 +03:00
1eaedeef0f рабочие графики на странице для разработчиков 2025-04-08 17:37:20 +03:00
f5db46b293 сделал сборку с предупреждением, если версия ревизии из гита не определена. привет, Даня! 2025-04-04 12:06:26 +03:00
39cd5e6359 микроизменения 2025-04-04 11:24:07 +03:00
9c62810e8e график хотя бы рисуется, но при обновлении страницы возникает переполнение стека 2025-04-04 10:54:25 +03:00
71bd388dce добавление обновлений OTA для TDMA терминала 2025-04-03 18:12:51 +03:00
70a2257255 обновил сбор статистики, теперь он выполняется отдельно от загрузки состояния и может работать на FULL RATE. К тому же, статистика доступна только на SCPC модеме 2025-04-03 15:26:03 +03:00
a204c4002b обновил control system client 2025-04-03 14:24:22 +03:00
940a2139b9 добавил в мониторинг TDMA-морды статус обновления 2025-04-03 14:23:08 +03:00
a6a4391123 исправления для защиты запуска вебки с некорректными аргументами командной строки 2025-04-02 13:36:54 +03:00
51431210f0 правильный тип поля в TDMA вебке 2025-04-01 17:51:35 +03:00
17110a82e0 багфиксы: сборка и число с запятыми на некоторых локалях в вебке 2025-04-01 17:47:08 +03:00
1b6b8b5329 изменил везде настройки CinC на настройки DPDI, что верно и что используется теперь в TDMA модеме 2025-04-01 17:18:34 +03:00
fe3b45dc9e добавил установку задержки до спутника в вебке TDMA 2025-04-01 14:55:25 +03:00
3a4c7363b3 обновил таблицу ОСШ 2025-02-11 16:44:04 +03:00
c5caa8efd8 исправил зависимость для работы с новым доменом гита 2025-02-04 18:04:31 +03:00
6122040752 попытка нарисовать график 2025-01-31 13:41:43 +03:00
8c527b4ae6 обновил журналирование для разработчиков 2025-01-30 11:25:34 +03:00
f8b5605ac3 обновил журналирование для разработчиков 2025-01-30 11:25:26 +03:00
a59c8e48d9 фича: имя веб-сервера 2025-01-30 10:59:37 +03:00
995a6156bc исправление верстки во вкладке статистики 2025-01-29 16:43:50 +03:00
77a159a1a8 таблица для просмотра логов 2025-01-29 16:32:31 +03:00
3f9f1ff839 исправил странное поведение ссылки на скачивание логов 2025-01-29 15:25:12 +03:00
24199ca03a обновил клиентскую библиотеку 2025-01-29 15:16:03 +03:00
d5123ef0a2 сделал страницу для разработчиков, добавил примитивный сбор журнала статистики 2025-01-29 15:15:26 +03:00
f9b919facf фикс некорректного ограничения центральной частоты 2025-01-27 14:04:03 +03:00
4d1b29b015 фикс установки адреса данных для режима работы L3 2025-01-23 17:13:57 +03:00
b3c4c273a6 микрофиксы модкода в статистике 2025-01-23 16:41:35 +03:00
1affd7d170 фича: devtool скрипт 2025-01-23 16:17:03 +03:00
73e1d281b2 фикс: расчетная скорость показывала "не число" 2025-01-23 15:25:46 +03:00
fdc4935d8c фикс: установка модкода 2025-01-23 15:17:48 +03:00
5f0959b521 добавление включения/выключения логгирования через скрипт 2025-01-22 16:03:15 +03:00
130c3c4dfc исправление багов: принудительная остановка ssl сервера, ошибка применения QoS профиля, ошибка применения последовательности голда, небольшие изменения в верстке мониторинга 2025-01-21 19:56:44 +03:00
c54c467f9d фича: логгирование в файл для релизной сборки 2025-01-21 16:08:15 +03:00
073bf43d8b фича: числа с разделением по тысячам 2025-01-21 15:41:10 +03:00
2c2cbf4249 вернул правильный коммит для control system 2025-01-21 11:07:38 +03:00
6be0d9cc2b исправил единицы измерения на православные 2025-01-20 18:43:26 +03:00
b8aee5625b обновление control system 2025-01-20 15:56:17 +03:00
3a1699d865 фикс косячных строк в json, фича: SNR в вебке 2025-01-20 15:55:03 +03:00
bafef9c51c добавление просмотра примерной скорости на интерфейсе в SCPC модеме 2025-01-20 14:14:56 +03:00
196f7ae5a2 исправление модкода 2025-01-20 13:31:45 +03:00
8813488df8 исправил тестовое состояние, исправил логику работы TDMA-морды, мелкие исправления в именовании параметров 2025-01-20 10:39:18 +03:00
6464cda437 фикс предупреждений о типах данных при преобразовании 2025-01-17 19:12:31 +03:00
3537965393 фича: автообновление сессии 2025-01-17 19:09:44 +03:00
a4214fd007 исправление настроек сети 2025-01-16 15:24:08 +03:00
a5a9b0c4e5 фикс установки параметров + фикс некоторых полей 2025-01-16 15:01:14 +03:00
790bfc06c2 вывод нормальной ошибки от сервера + фикс установки режима работы передатчика 2025-01-16 13:50:01 +03:00
cbd2adc1c8 исправил ссылку в подмодулях 2025-01-16 13:18:21 +03:00
1b8fd0d0bc фикс ошибки установки параметров 2025-01-16 13:14:16 +03:00
e3a4bb8256 мелкие исправления интерфейса + фикс rolloff 2025-01-16 11:46:21 +03:00
90c02eb63a компилируемая версия для модема, ее нельзя собрать на компе из-за различий в библиотеке boost::asio 2025-01-15 17:43:39 +03:00
e313027759 патч для работающей настройки "Последовательность Голда" 2025-01-15 17:27:51 +03:00
136d8dbb5b Merge branch 'refs/heads/dev-front-generator'
# Conflicts:
#	src/terminal_api_driver.cpp
#	static/main-scpc.html
2025-01-15 17:21:31 +03:00
456faedf7d косметические изменения 2025-01-15 17:11:49 +03:00
2c9d513613 добавил установку всех параметров 2025-01-15 17:11:38 +03:00
46497bfda0 работает получение параметров от бекенда (проверено SCPC и TDMA) 2025-01-15 12:00:29 +03:00
0982544c2e больше данных о системе в состоянии устройства (load average + RAM total/free) 2025-01-15 10:12:30 +03:00
5a94f9a4fd генератор фронта завершен 2025-01-14 17:42:38 +03:00
4df06ee57b фикс задержки в канале CinC 2025-01-14 15:22:09 +03:00
670780e328 добавил последовательность голда 2025-01-14 15:15:35 +03:00
25a3b11ba8 рабочая генерация всех полей на вкладке настроек и Qos, осталось администрирование 2025-01-14 14:42:16 +03:00
bf2d374705 работающая генерация базовых полей (числа, select, checkbox) + законченные настройки для TDMA 2025-01-13 18:34:19 +03:00
a7242c186d работающая генерация настроек 2025-01-10 18:10:14 +03:00
44aec3a114 миграция на vue.js 2.7->3.5; рабочий front-generator (только для мониторинга и QoS) 2025-01-10 14:28:53 +03:00
872b5e7b3d фикс входа в браузере opera gx 2025-01-09 17:43:02 +03:00
fc121c51b2 коллапс ячеек в таблицах 2025-01-09 14:03:08 +03:00
479200df9e изменил формат аптайма 2025-01-09 12:49:59 +03:00
20cf08e2a1 добавил получение sysinfo и вывод аптайма в веб-морде 2025-01-09 12:02:42 +03:00
ab654a754c патч для переподключения к API в случае ошибок 2025-01-09 11:28:35 +03:00
8990fed8f0 зачатки страницы для разработчиков 2024-12-28 16:42:14 +03:00
be6c8023c5 добавил защиту от двойного обновления прошивки, добавил настройки TCP-акселерации в веб (они пока не работает из-за API) 2024-12-28 16:41:58 +03:00
24cb1061a7 добавил отключенную скорость кода 3/5 2024-12-28 11:20:37 +03:00
77ba05e407 косметические исправления: увеличил шрифт, убрал жирный шрифт в таблицах 2024-12-25 11:05:22 +03:00
f3454897d8 исправление полей в SCPC модеме 2024-12-24 17:21:04 +03:00
dc4e37eb8a Merge branch 'refs/heads/dev-scpc-new-rolloff' 2024-12-23 13:57:59 +03:00
522abee794 косметические исправления страниц 2024-12-23 12:52:52 +03:00
8278e4119f косметические исправления страниц 2024-12-23 09:48:02 +03:00
0a3a282d0f некоторые нововведения в front-generator 2024-12-23 09:40:43 +03:00
3d97824ee7 фикс модкода CCM 2024-12-23 09:39:50 +03:00
55fa498dc1 обновил поле rolloff 2024-12-16 09:39:39 +03:00
f5c5caa31c добавил зачатки front-generator, исправил мелкий баг QoS 2024-12-16 09:30:16 +03:00
833374a80e сделал API статической библиотекой 2024-11-29 17:52:25 +03:00
b67011b9a3 добавил получение статуса из API вместе со статистикой 2024-11-29 17:37:25 +03:00
91e9c0301e фикс: билд SCPC модема 2024-11-29 15:12:27 +03:00
98dcc06a6a логгирование ошибок API (установка параметров) 2024-11-29 15:04:38 +03:00
e0aacfe8aa логгирование ошибок API на всех периодических запросах 2024-11-29 14:11:36 +03:00
3e4ffc8281 изменил логику подключения к API 2024-11-29 13:36:39 +03:00
1f8ea04f43 добавил динамическую линковку для boost::log 2024-11-28 17:43:41 +03:00
ad6d734f4a скрыл ржачную картинку 2024-11-28 16:58:57 +03:00
6d79d60eb1 исправил надпись 2024-11-28 16:56:34 +03:00
572a2583f0 логика работы TDMA 2024-11-28 16:56:20 +03:00
925fec6dda добавил фронт для TDMA 2024-11-28 14:57:29 +03:00
5f3d5791da исправление замечаний от Кости 2024-11-27 11:20:55 +03:00
c05a9cff7a добавил повторные попытки подключения к API, если не удалось подключиться 2024-11-15 15:49:20 +03:00
dff0ba1cd3 фикс: центральная частота 2024-11-15 15:37:08 +03:00
43f35da9a2 фикс: цвета в темной теме 2024-11-15 15:22:56 +03:00
ac04c0545b фикс: версия ПО и прочее не показывалось в вебке 2024-11-15 15:12:19 +03:00
3e46f82c0e фикс: билда релизной версии 2024-11-15 14:30:02 +03:00
1536914888 фикс: не использовался путь для статических файлов + немного поменял цвета 2024-11-15 14:26:30 +03:00
1a80e9d455 фикс: не использовался путь для статических файлов 2024-11-15 14:15:10 +03:00
e2618e0300 обновление vue js 2024-11-15 13:50:14 +03:00
1d73547eae cleanup + изменение цветов темы 2024-11-15 13:45:25 +03:00
4a27a46c27 фикс работы с синхронизацией потоков 2024-11-15 10:55:52 +03:00
55448c2bfe поменял название полей у QoS, чтобы прасер был проще 2024-11-15 10:38:52 +03:00
87725ad20a фикс ошибок: пустая строка qos.class.filters.proto в запросе, не применяющиеся настройки сети, нет шага у "ACM*" и "*ослабление" 2024-11-15 10:13:21 +03:00
cc354b73e3 фикс ошибок: пустая строка qos.class.filters.proto в запросе, не применяющиеся настройки сети, нет шага у "ACM*" и "*ослабление" 2024-11-15 09:50:43 +03:00
200dfef698 мелкие исправления предупреждений 2024-11-14 17:30:33 +03:00
5ab16a89db фикс действий с системой 2024-11-14 16:59:41 +03:00
e27164a8b3 исправил шаг в полях с частотой 2024-11-14 16:45:54 +03:00
ccc7766e88 кучка мелких фиксов + добавление перезагрузки модема и сброса настроек 2024-11-14 16:42:24 +03:00
6d076f03cd сделал загрузку обновленной прошивки и само обновление раздельными 2024-11-14 15:42:05 +03:00
ed1bd12c95 фикс бага с настройками сети в отладочной версии API 2024-11-14 15:18:18 +03:00
515a05ec9b добавил получение версии ПО 2024-11-14 15:10:32 +03:00
eda26319c4 добавил sha256 для файла обновления 2024-11-14 11:34:28 +03:00
0dcc562b7d добавил обновление прошивки из веб морды 2024-11-14 11:09:53 +03:00
6467333846 рефактор параметров QoS и tcp-акселерации 2024-11-14 09:48:57 +03:00
484a6abe08 добавил все настройки в веб, сделал cleanup интерфейса 2024-11-13 17:44:42 +03:00
9577ac844d исправления типов параметров в JS 2024-11-13 11:37:06 +03:00
ed0bfce64d фикс http keep alive 2024-11-13 11:29:31 +03:00
8da8c054bf фикс ошибок релизной сборки и парсинга большого тела запроса 2024-11-13 11:25:48 +03:00
90b1f221ea фикс ошибок применения rx/tx параметров 2024-11-12 17:04:06 +03:00
df4b990316 добавил изменение параметров rx/tx + изменил верстку блоков с настройками 2024-11-12 17:01:17 +03:00
dc2d464f41 добавил изменение параметров dpdi (CinC) 2024-11-12 15:37:58 +03:00
087da149f1 изменил политику кеширования, убрал строковые константы из статистик (пилоты, размер кадра) 2024-11-12 14:13:07 +03:00
c0e7e1e300 окно подтверждения применения настроек BUC и LNB, фикс нулевой длинны HTTP запроса 2024-11-12 13:34:59 +03:00
800473b4e3 окно подтверждения применения настроек BUC и LNB 2024-11-12 13:22:58 +03:00
dd0a6813a8 добавил правила QoS по-умолчанию (пустой набор правил) 2024-11-12 11:39:09 +03:00
e2c9877017 фикс некорректного ответа QoS 2024-11-12 11:08:22 +03:00
b51e303006 фикс некорректного ответа QoS 2024-11-12 11:07:22 +03:00
00aa17bb37 добавил корректное сохранение конфига 2024-11-12 11:04:52 +03:00
5175362d1b сделал систему прав, теперь все действия с апи выполняются только при наличии прав (и в целом авторизации) 2024-11-12 10:56:26 +03:00
857a01528b добавил запись настроек BucLnb 2024-11-12 10:18:03 +03:00
cb9d412c8e куча изменений, но зато теперь сохраняются настройки QoS и есть алгоритм сохранения параметров в API 2024-11-11 17:35:25 +03:00
435f215118 сделал включение/отключение классов QoS и правил QoS 2024-11-11 10:45:01 +03:00
4c1ce6031b сделал вторую версию прототипа для QoS, осталось для нее только сохранение прикрутить 2024-11-08 18:08:47 +03:00
1f5eb54225 добавил применение buc lnb для теста 2024-11-08 17:50:02 +03:00
d1ad7baad1 прототип QoS 2024-11-08 17:11:14 +03:00
16b9776bfd визуальные изменения в вебе 2024-11-08 15:34:05 +03:00
a833c0f68a сделал кеширование статистики и настроек терминала 2024-11-08 12:14:09 +03:00
fae7a2ffc8 куча мелких изменений в вебе 2024-11-07 18:07:06 +03:00
4a293e10f6 сделал чтение настроек, удалил пару рудиментов 2024-11-07 13:59:20 +03:00
eaee827261 нововведение: QoS (пока пустой). добавил чтение настроек модема, пока что оно не работает в браузере и не все параметры есть. найден баг с крашем приложения при получении параметров dpdm и acm 2024-11-06 18:00:29 +03:00
6f62c3e1fa сделал получение статистики CinC только когда модем в режиме CinC 2024-11-05 17:19:17 +03:00
40e12f1c67 исправления логики веб-страницы и статистики 2024-11-05 17:11:36 +03:00
541b08a40e переход на получение статистики устройства новым API библиотеки control_system_client 2024-11-05 16:06:16 +03:00
5bfc5cebaf мелкие изменения во фронте 2024-11-05 14:39:26 +03:00
b2ed7ab015 фикс: зависания соединения с протоколом http 2024-11-05 11:31:20 +03:00
3714207983 работающий logout 2024-11-05 10:44:36 +03:00
0eacd76810 работающая авторизация 2024-11-05 10:33:52 +03:00
b561dedb2b почти рабочая авторизация. оказывается сейчас нет payload у запроса, поэтому невозможно распарсить из него json. 2024-11-04 17:57:47 +03:00
0b794fac40 рефактор статических файлов, добавлено удобное API и поддержка кеширования 2024-11-04 14:17:34 +03:00
82b433c447 описал интерфейсную часть аутентификации, пока оставил без реализации 2024-11-04 11:44:35 +03:00
d9967b69e8 добавил кучу всего в вебе 2024-11-01 18:07:53 +03:00
82 changed files with 52628 additions and 15934 deletions

7
.gitignore vendored
View File

@@ -4,3 +4,10 @@ cmake-build-*
cert.pem
key.pem
dh.pem
/web-action
# эти файлы после генерации должны быть перемещены в `/static`
front-generator/main-*.html
# логи сервера в релизной версии
http_server_*.log

26
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,26 @@
stages:
- build
test for build:
stage: build
image: localhost:5000/cpp-test-universal:latest
tags:
- cpp-test-universal
only:
- master
variables:
GIT_SUBMODULE_STRATEGY: recursive
script:
- cmake -DCMAKE_BUILD_TYPE=Debug -DMODEM_TYPE=TDMA -B cmake-build-debug-tdma
- cmake -DCMAKE_BUILD_TYPE=Debug -DMODEM_TYPE=SCPC -B cmake-build-debug-scpc
- cmake -DCMAKE_BUILD_TYPE=Debug -DMODEM_TYPE=SHPS -B cmake-build-debug-shps
- cmake -DCMAKE_BUILD_TYPE=Release -DMODEM_TYPE=TDMA -B cmake-build-release-tdma
- cmake -DCMAKE_BUILD_TYPE=Release -DMODEM_TYPE=SCPC -B cmake-build-release-scpc
- cmake -DCMAKE_BUILD_TYPE=Release -DMODEM_TYPE=SHPS -B cmake-build-release-shps
- cmake --build cmake-build-debug-tdma -j 4
- cmake --build cmake-build-debug-scpc -j 4
- cmake --build cmake-build-debug-shps -j 4
- cmake --build cmake-build-release-tdma -j 4
- cmake --build cmake-build-release-scpc -j 4
- cmake --build cmake-build-release-shps -j 4

9
.gitmodules vendored Normal file
View File

@@ -0,0 +1,9 @@
[submodule "dependencies/control_system_client"]
path = dependencies/control_system_client
url = http://gitlab.devrss.vg/mf-tdma/protocol_processing/control_system_client.git
[submodule "dependencies/control_system_client_tdma"]
path = dependencies/control_system_client_tdma
url = http://gitlab.devrss.vg/mf-tdma/protocol_processing/control_system_client.git
[submodule "dependencies/control_system_client_shps"]
path = dependencies/control_system_client_shps
url = http://gitlab.devrss.vg/mf-tdma/protocol_processing/control_system_client.git

View File

@@ -15,11 +15,57 @@ else()
message(FATAL_ERROR "You must set build type \"Debug\" or \"Release\". Another build types not supported!")
endif()
add_compile_options(-Wall -Wextra -Wsign-conversion)
if("${MODEM_TYPE}" STREQUAL "SCPC")
add_definitions(-DMODEM_IS_SCPC)
message(STATUS "Selected SCPC modem")
add_subdirectory(dependencies/control_system_client) # подключение правильного control system client SCPC, ветка main
elseif ("${MODEM_TYPE}" STREQUAL "TDMA")
add_definitions(-DMODEM_IS_TDMA)
message(STATUS "Selected TDMA modem")
add_subdirectory(dependencies/control_system_client_tdma) # подключение правильного control system client TDMA, ветка terminal-tdma
elseif ("${MODEM_TYPE}" STREQUAL "SHPS")
add_definitions(-DMODEM_IS_SHPS)
message(STATUS "Selected SHPS modem")
add_subdirectory(dependencies/control_system_client_shps) # подключение правильного control system client SHPS, ветка shps
else()
message(FATAL_ERROR "You must set `MODEM_TYPE` \"SCPC\" or \"TDMA\" or \"SHPS\"!")
endif()
add_subdirectory(dependencies/control_system)
SET(PROJECT_GIT_REVISION "0")
FIND_PACKAGE(Git)
IF (GIT_FOUND)
EXECUTE_PROCESS (
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HEAD
# ERROR_VARIABLE ERROR_RESULT
# RESULT_VARIABLE INFO_RESULT
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
IF ( ${GIT_HEAD} MATCHES "^.+$" )
STRING ( SUBSTRING ${GIT_HEAD} 0 8 VERSION_REVISION )
SET ( PROJECT_GIT_REVISION ${VERSION_REVISION} )
ENDIF()
ENDIF()
add_compile_options(-Wall -Wextra -Wsign-conversion -DPROJECT_GIT_REVISION="${PROJECT_GIT_REVISION}")
# максимальный размер тела запроса 200mb
add_definitions(-DHTTP_MAX_PAYLOAD=200000000)
include_directories(src/)
add_executable(terminal-web-server
src/api-driver/daemon.h
src/api-driver/daemon.cpp
src/api-driver/proxy.h
src/api-driver/proxy.cpp
src/api-driver/structs.h
src/api-driver/structs.cpp
src/common/nlohmann/json.hpp
src/server/mime_types.hpp
src/server/mime_types.cpp
src/server/request_parser.hpp
@@ -38,8 +84,16 @@ add_executable(terminal-web-server
src/terminal_api_driver.cpp
src/auth/resources.cpp
src/auth/resources.h
src/auth/jwt.cpp
src/auth/jwt.h
src/auth/utils.cpp
src/auth/utils.h
src/version.h
src/api-driver/stricts-enable.h
)
add_definitions(-DBOOST_LOG_DYN_LINK)
find_package(Boost 1.53.0 COMPONENTS system thread filesystem log log_setup REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(terminal-web-server ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} terminal-client-api)

View File

@@ -1,36 +0,0 @@
cmake_minimum_required(VERSION 3.20)
project(terminal-client-api)
set(CMAKE_CXX_STANDARD 17)
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
else()
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
endif()
include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS -fPIC)
set(default_build_type "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -s -DNDEBUG ")
message(${CMAKE_CXX_FLAGS})
message("CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
message(${CMAKE_CXX_FLAGS})
add_library(terminal-client-api SHARED
client/main.cpp
client/sock_client.cpp
client/system_client.cpp
)
target_include_directories(terminal-client-api PUBLIC "include/")
find_package(Boost 1.53.0 COMPONENTS system log log_setup REQUIRED)
find_package(cereal REQUIRED)
target_link_libraries(terminal-client-api ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} cereal::cereal)
target_include_directories(terminal-client-api PRIVATE ${Boost_INCLUDE_DIR})

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
#include "sock_client.h"
client::client(boost::asio::io_context & io_context, const std::string & unix_file, std::function<void(const std::vector<uint8_t>&)> func_cb)
: socket_(io_context)
, callback_func(func_cb)
{
socket_.async_connect(seq_packet::seqpacket_protocol::endpoint(unix_file),
std::bind(&client::handle_connect, this,
std::placeholders::_1));
}
client::~client()
{
do_close();
}
void client::write(const std::vector<uint8_t> & data)
{
boost::asio::write(socket_, boost::asio::buffer(data));
}
void client::do_close()
{
socket_.close();
}
void client::start()
{
seq_packet::seqpacket_protocol::socket::message_flags in_flags { MSG_WAITALL };
socket_.async_receive(boost::asio::buffer(data_), in_flags,
std::bind(&client::handle_read, this,
std::placeholders::_1,
std::placeholders::_2));
}
void client::handle_connect(const boost::system::error_code & error)
{
if (!error)
start();
}
void client::handle_read(const boost::system::error_code & error, size_t bytes_transferred)
{
if (!error && callback_func && bytes_transferred)
{
callback_func(std::vector<uint8_t>(std::begin(data_), std::begin(data_) + bytes_transferred));
start();
}
//else
// do_close();
}

View File

@@ -1,24 +0,0 @@
#pragma once
#include "../common/seq_packet.h"
class client
{
public:
client(boost::asio::io_context & io_context, const std::string & unix_file, std::function<void(const std::vector<uint8_t>&)> func_cb = nullptr);
~client();
void write(const std::vector<uint8_t> & data);
void do_close();
private:
client(const client&) = delete;
const client& operator=(const client&) = delete;
seq_packet::seqpacket_protocol::socket socket_;
std::array<uint8_t, 2000> data_;
std::function<void(const std::vector<uint8_t>&)> callback_func;
void handle_connect(const boost::system::error_code & error);
void handle_read(const boost::system::error_code & error, size_t bytes_transferred);
void start();
};

View File

@@ -1,755 +0,0 @@
#include "system_client.h"
system_client::system_client(const std::string & unix_file, std::function<void(const std::string&)> log_func)
: clt(io_context, unix_file, std::bind(&system_client::data_received, this, std::placeholders::_1))
, log(log_func)
, ctx_thread([&] { io_context.run(); } )
, cmd_id(0)
, current_state(state::connected)
{
}
system_client::~system_client()
{
io_context.stop();
ctx_thread.join();
}
state system_client::get_current_state() const
{
return current_state;
}
response_type system_client::send_login_cmd(const std::string & username, const std::string & pass, access_rights & access)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header login_cmd_hdr { curr_id, cmd_type::login };
login_cmd log_req { username, pass };
send_to_socket(login_cmd_hdr, log_req);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::login, data_from_serv);
access = access_rights::not_allowed;
if (data_from_serv.has_value())
access = std::any_cast<access_rights>(data_from_serv);
return result;
}
response_type system_client::send_copy_cmd(const fm::side_description & src, const fm::side_description & dst)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header copy_cmd_hdr { curr_id, cmd_type::copy };
copy_cmd copy_req { src, dst };
send_to_socket(copy_cmd_hdr, copy_req);
return wait_for_progressing_response(curr_id, cmd_type::copy);
}
response_type system_client::send_ping_cmd(const std::string & ip, size_t count)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header ping_cmd_hdr { curr_id, cmd_type::ping };
ping_cmd ping_req { ip, count };
send_to_socket(ping_cmd_hdr, ping_req);
return wait_for_progressing_response(curr_id, cmd_type::ping);
}
response_type system_client::send_traceroute_cmd(const std::string & ip)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header tracert_cmd_hdr { curr_id, cmd_type::tracert };
tracert_cmd tracert_req { ip };
send_to_socket(tracert_cmd_hdr, tracert_req);
return wait_for_progressing_response(curr_id, cmd_type::tracert);
}
response_type system_client::send_show_interface_cmd(const interface_value & interface)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header interface_cmd_hdr { curr_id, cmd_type::interface };
interface_cmd interface_req { interface };
send_to_socket(interface_cmd_hdr, interface_req);
return wait_for_progressing_response(curr_id, cmd_type::interface);
}
response_type system_client::wait_for_progressing_response(uint32_t c_id, const cmd_type & cmd_t)
{
auto result = response_type::in_progress;
while (result != response_type::ok && result != response_type::error && result != response_type::abort)
{
std::any data_from_serv;
result = wait_for_response(c_id, cmd_t, data_from_serv);
if (data_from_serv.has_value() && stdout_cb)
{
auto & text_bytes = std::any_cast<std::vector<uint8_t>&>(data_from_serv);
std::scoped_lock lock(cb_mtx);
stdout_cb(reinterpret_cast<char *>(text_bytes.data()), text_bytes.size());
}
}
if (result == response_type::abort)
{
std::scoped_lock lock_1(responses_mtx);
responses.erase(c_id);
responses_data.erase(c_id);
}
return result;
}
void system_client::abort()
{
send_abort(cmd_id);
process_response(cmd_id, response_type::abort, {});
}
void system_client::send_abort(uint32_t id)
{
std::stringstream s;
cmd_header abort_cmd { id, cmd_type::abort };
{
cereal::BinaryOutputArchive oarchive(s);
oarchive(abort_cmd);
}
std::vector<uint8_t> data((std::istream_iterator<uint8_t>(s)), std::istream_iterator<uint8_t>());
clt.write(data);
}
response_type system_client::send_logout_cmd()
{
if (current_state != state::logged_in)
return response_type::error;
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { cmd_id++ };
cmd_header logout_cmd_hdr { curr_id, cmd_type::exit };
std::stringstream s;
{
cereal::BinaryOutputArchive oarchive(s);
oarchive(logout_cmd_hdr);
}
std::vector<uint8_t> data((std::istream_iterator<uint8_t>(s)), std::istream_iterator<uint8_t>());
clt.write(data);
std::any data_from_serv;
return wait_for_response(curr_id, cmd_type::exit, data_from_serv);
}
response_type system_client::wait_for_response(uint32_t id, const cmd_type & type, std::any & data)
{
std::unique_lock<std::mutex> lock(responses_mtx);
cv_resp.wait(lock, [&] { return responses.find(id) != responses.end(); } );
auto result = std::move(responses[id].front());
data = std::move(responses_data[id].front());
responses[id].pop();
responses_data[id].pop();
if (result != response_type::abort && responses[id].empty())
{
responses.erase(id);
responses_data.erase(id);
}
if (result == response_type::ok)
{
switch (type)
{
case cmd_type::login:
{
current_state = state::logged_in;
break;
}
case cmd_type::exit:
{
current_state = state::disconnected;
clt.do_close();
break;
}
default: break;
}
}
return result;
}
void system_client::process_response(uint32_t id, response_type resp, std::any && data)
{
{
std::scoped_lock lock(responses_mtx);
if (responses.find(id) != responses.end() && responses[id].empty() && responses[id].front() == response_type::abort)
return;
responses[id].push(resp);
responses_data[id].push(std::move(data));
}
cv_resp.notify_all();
}
void system_client::data_received(const std::vector<uint8_t> & data)
{
std::stringstream ss;
std::copy(data.begin(), data.end(), std::ostream_iterator<uint8_t>(ss));
cereal::BinaryInputArchive iarchive(ss);
response_header cmd;
iarchive(cmd);
std::any data_from_serv;
switch (cmd.cmd)
{
case cmd_type::login:
{
if (cmd.rsp == response_type::ok)
{
access_rights access;
iarchive(access);
data_from_serv = access;
}
break;
}
case cmd_type::ping:
case cmd_type::tracert:
case cmd_type::interface:
{
if (cmd.rsp == response_type::in_progress)
{
std::vector<uint8_t> data_rsp;
iarchive(data_rsp);
data_from_serv = std::move(data_rsp);
}
break;
}
case cmd_type::get_demodulator_frequency:
{
if (cmd.rsp == response_type::ok)
{
uint32_t freq;
iarchive(freq);
data_from_serv = freq;
}
break;
}
case cmd_type::get_gain_param:
{
if (cmd.rsp == response_type::ok)
{
double gain;
iarchive(gain);
data_from_serv = gain;
}
break;
}
case cmd_type::zynq_param:
{
if (cmd.rsp == response_type::ok)
{
double value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_demodulator_param:
{
if (cmd.rsp == response_type::ok)
{
uint32_t value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_modulator_param:
{
if (cmd.rsp == response_type::ok)
{
uint32_t value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_level_dmd:
{
if (cmd.rsp == response_type::ok)
{
double value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_acm_params:
{
if(cmd.rsp == response_type::ok)
{
cmd_get_acm_param value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_dma_debugg:
{
if (cmd.rsp == response_type::ok)
{
std::string value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_params_dpdi:
{
if (cmd.rsp == response_type::ok)
{
dpdi_parameters value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_network:
{
if (cmd.rsp == response_type::ok)
{
std::string value;
iarchive(value);
data_from_serv = value;
}
break;
}
case cmd_type::get_qos_settings:
{
if (cmd.rsp == response_type::ok)
{
std::string value;
iarchive(value);
data_from_serv = value;
}
break;
}
default: break;
}
process_response(cmd.id, cmd.rsp, std::move(data_from_serv));
}
void system_client::set_stdout_callback(std::function<void(const char *, uint32_t)> cb)
{
std::scoped_lock lock(cb_mtx);
stdout_cb = cb;
}
response_type system_client::send_set_dem_freq_cmd(uint32_t freq)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header set_dem_freq_cmd_hdr { curr_id, cmd_type::set_demodulator_frequency };
set_dem_freq_cmd freq_set { freq };
send_to_socket(set_dem_freq_cmd_hdr, freq_set);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::set_demodulator_frequency , data_from_serv);
return result;
}
response_type system_client::send_get_dem_freq_cmd(uint32_t & freq)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header get_dem_freq_cmd_hdr { curr_id, cmd_type::get_demodulator_frequency };
send_to_socket(get_dem_freq_cmd_hdr);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_demodulator_frequency , data_from_serv);
if (data_from_serv.has_value())
freq = std::any_cast<uint32_t>(data_from_serv);
return result;
}
response_type system_client::send_get_gain_param(const gain_value & interface_gain, double & gain)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header get_gain_cmd_hdr{curr_id,cmd_type::get_gain_param};
get_gain_par get_gain_cmd{interface_gain};
send_to_socket(get_gain_cmd_hdr,get_gain_cmd);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_gain_param , data_from_serv);
if (data_from_serv.has_value())
gain = std::any_cast<double>(data_from_serv);
return result;
}
response_type system_client::send_get_demodulator_param(const getdemodulator_value & demod_val, uint32_t & value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header get_demod_param{curr_id,cmd_type::get_demodulator_param};
cmd_get_demodulator_param get_dmd_param{demod_val, 0};
send_to_socket(get_demod_param,get_dmd_param);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_demodulator_param , data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<uint32_t>(data_from_serv);
return result;
}
response_type system_client::send_get_modulator_param(const modulator_value & mod_val, uint32_t & value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header get_mod_param{curr_id,cmd_type::get_modulator_param};
cmd_get_modulator_param cmd_get_mod_param{mod_val, 0};
send_to_socket(get_mod_param,cmd_get_mod_param);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_modulator_param , data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<uint32_t>(data_from_serv);
return result;
}
response_type system_client::send_set_gain_param(const gain_value & interface_gain, double gain)
{
std::scoped_lock lock (cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header set_gain_cmd_hdr{curr_id, cmd_type::set_gain_param};
set_gain_par set_gain_cmd{interface_gain, gain};
send_to_socket(set_gain_cmd_hdr,set_gain_cmd);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::set_gain_param , data_from_serv);
return result;
}
response_type system_client::send_radio_enable(const cmd_radio & enable_choice, bool enbl_)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header radio_enable_cmd_hdr{curr_id, cmd_type::radio_on_of};
radio_enable radio_enable_cmd{enable_choice, enbl_};
send_to_socket(radio_enable_cmd_hdr, radio_enable_cmd);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::radio_on_of, data_to_serv);
return result;
}
response_type system_client::send_set_demodulator_param(const demodulator_value & demod_val, uint32_t value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header demodulator_cmd_hdr{curr_id, cmd_type::set_demodulator_param};
cmd_set_demodulator_param demodulator_cmd{demod_val, value};
send_to_socket(demodulator_cmd_hdr, demodulator_cmd);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_demodulator_param, data_to_serv);
return result;
}
response_type system_client::send_modulator_param(const modulator_value & mod_val, uint32_t value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header modulator_cmd_hdr{curr_id, cmd_type::modulator_param};
cmd_set_modulator_param modulator_cmd{mod_val, value};
send_to_socket(modulator_cmd_hdr, modulator_cmd);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::modulator_param, data_to_serv);
return result;
}
response_type system_client::send_zynq_cmd(const zynq_value & zynq_val, double & value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header zynq_cmd_hdr{curr_id, cmd_type::zynq_param};
cmd_zynq_param zynq_cmd{zynq_val};
send_to_socket(zynq_cmd_hdr, zynq_cmd);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::zynq_param, data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<double>(data_from_serv);
return result;
}
response_type system_client::send_rollof_and_baudrate(double & rollof, double &baudrate)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header roll_baud_cmd_hdr{curr_id, cmd_type::set_baudrate_rollof_dmd};
cmd_set_rollof_and_demod cmd_roll_baud{baudrate, rollof};
send_to_socket(roll_baud_cmd_hdr, cmd_roll_baud);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_baudrate_rollof_dmd, data_to_serv);
return result;
}
response_type system_client::send_get_dma_debug(const cmd_get_dma_debugg_enum & dma_debug, std::string &value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header get_dma_debug_header{curr_id, cmd_type::get_dma_debugg};
cmd_get_dma_debug val_cmd{value, dma_debug};
send_to_socket(get_dma_debug_header, val_cmd);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_dma_debugg, data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<std::string>(data_from_serv);
return result;
}
response_type system_client::send_get_level_dem(const cmd_level_dem & lvl_dem_val, double &value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header snr_cmd_hdr{curr_id, cmd_type::get_level_dmd};
cmd_get_level_dem val_cmd{value, lvl_dem_val};
send_to_socket(snr_cmd_hdr, val_cmd);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_level_dmd, data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<double>(data_from_serv);
return result;
}
response_type system_client::send_get_acm_params(cmd_get_acm_param &acm_params)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_acm_header{curr_id, cmd_type::get_acm_params};
send_to_socket(cmd_acm_header, acm_params);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_acm_params, data_from_serv);
if (data_from_serv.has_value())
acm_params = std::any_cast<cmd_get_acm_param>(data_from_serv);
return result;
}
response_type system_client::send_set_qos_params(const std::string &node, const name_classes_qos & class_qos)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_qos_header{curr_id, cmd_type::set_qos_settings};
cmd_set_qos_settings qos_settings{node, class_qos};
send_to_socket(cmd_qos_header, qos_settings);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_qos_settings, data_to_serv);
return result;
}
response_type system_client::send_get_qos_params(std::string &node, const name_classes_qos & class_qos)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_qos_header{curr_id, cmd_type::get_qos_settings};
cmd_get_qos_settings qos_settings{node, class_qos};
send_to_socket(cmd_qos_header, qos_settings);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_qos_settings, data_from_serv);
if (data_from_serv.has_value())
node = std::any_cast<std::string>(data_from_serv);
return result;
}
response_type system_client::send_set_10g_config(const cmd_10g_config & _10g_config, std::string &value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header _10g_config_header{curr_id, cmd_type::set_10g_config};
cmd_set_10g_config _10g_config_cmd{value, _10g_config};
send_to_socket(_10g_config_header, _10g_config_cmd);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_10g_config, data_to_serv);
return result;
}
response_type system_client::send_set_dma_debug(const cmd_dma_debugg & dma_debugg, std::string &value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header dma_debug_header{curr_id, cmd_type::set_dma_debugg};
cmd_set_dma_debug cmd_dma_debug{value, dma_debugg};
send_to_socket(dma_debug_header, cmd_dma_debug);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_dma_debugg, data_to_serv);
return result;
}
response_type system_client::send_set_lbq_params(const uint32_t & tick_ms, const uint32_t & bucket_size)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header lbq_debug_header{curr_id, cmd_type::set_lbq_params};
cmd_lbq_params params{tick_ms, bucket_size};
send_to_socket(lbq_debug_header, params);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_lbq_params, data_to_serv);
return result;
}
response_type system_client::send_set_acm_params(const ACM_parameters_serv &acm_params)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_acm_header{curr_id, cmd_type::set_acm_params};
cmd_set_acm_param cmd_set_acm{acm_params};
send_to_socket(cmd_acm_header, cmd_set_acm);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_acm_params, data_to_serv);
return result;
}
response_type system_client::send_set_dpdi_params(dpdi_parameters &dpdi_params)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_dpdi_header{curr_id, cmd_type::set_params_dpdi};
dpdi_parameters cmd_set_dpdi{dpdi_params};
send_to_socket(cmd_dpdi_header, cmd_set_dpdi);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_params_dpdi, data_to_serv);
return result;
}
response_type system_client::send_get_dpdi_params(dpdi_parameters &dpdi_params)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_dpdi_header{curr_id, cmd_type::get_params_dpdi};
send_to_socket(cmd_dpdi_header, dpdi_params);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_params_dpdi, data_from_serv);
if (data_from_serv.has_value())
dpdi_params = std::any_cast<dpdi_parameters>(data_from_serv);
return result;
}
response_type system_client::send_network_settings(const network_value & cmd_netw, const std::string & value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header network_cmd_hdr{curr_id, cmd_type::set_network};
cmd_set_network cmd_network{value, cmd_netw};
send_to_socket(network_cmd_hdr, cmd_network);
std::any data_to_serv;
auto result = wait_for_response(curr_id, cmd_type::set_network, data_to_serv);
return result;
}
response_type system_client::send_get_network_settings(const network_value & cmd_netw, std::string & value)
{
std::scoped_lock lock(cmd_in_progress_mtx);
uint32_t curr_id { ++cmd_id };
cmd_header cmd_network_hdr{curr_id, cmd_type::get_network};
cmd_get_network cmd_network{value, cmd_netw};
send_to_socket(cmd_network_hdr, cmd_network);
std::any data_from_serv;
auto result = wait_for_response(curr_id, cmd_type::get_network, data_from_serv);
if (data_from_serv.has_value())
value = std::any_cast<std::string>(data_from_serv);
return result;
}

View File

@@ -1,105 +0,0 @@
#pragma once
#include <map>
#include <queue>
#include <any>
#include "sock_client.h"
#include "../common/protocol_commands.h"
enum class state
{
disconnected,
connected,
logged_in
};
class system_client
{
public:
system_client(const std::string & unix_file, std::function<void(const std::string&)> log_func = nullptr);
~system_client();
state get_current_state() const;
response_type send_login_cmd (const std::string & username, const std::string & pass, access_rights & access);
response_type send_logout_cmd ();
response_type send_ping_cmd (const std::string & ip, size_t count);
response_type send_traceroute_cmd (const std::string & ip);
response_type send_show_interface_cmd(const interface_value & interface);
response_type send_copy_cmd (const fm::side_description & src, const fm::side_description & dst);
response_type send_set_dem_freq_cmd(uint32_t freq);
response_type send_get_dem_freq_cmd(uint32_t & freq);
response_type send_set_gain_param (const gain_value & interface_gain, double gain);
response_type send_get_gain_param (const gain_value & interface_gain, double & gain);
response_type send_radio_enable (const cmd_radio & enable_choice, bool enbl_);
response_type send_modulator_param(const modulator_value & mod_val, uint32_t value);
response_type send_get_modulator_param(const modulator_value & mod_val, uint32_t &value);
response_type send_set_demodulator_param(const demodulator_value & demod_val, uint32_t value);
response_type send_get_demodulator_param(const getdemodulator_value & demod_val, uint32_t &value);
response_type send_zynq_cmd(const zynq_value & zynq_val, double & value);
response_type send_network_settings(const network_value & cmd_netw, const std::string & value);
response_type send_get_network_settings(const network_value & cmd_netw, std::string & value);
response_type send_rollof_and_baudrate(double & rollof, double &baudrate);
response_type send_get_level_dem(const cmd_level_dem & lvl_dem_val, double &value);
response_type send_get_dma_debug(const cmd_get_dma_debugg_enum & dma_debug, std::string &value);
response_type send_set_10g_config(const cmd_10g_config & _10g_config, std::string &value);
response_type send_set_dma_debug(const cmd_dma_debugg & dma_debugg, std::string &value);
response_type send_set_acm_params(const ACM_parameters_serv &acm_params);
response_type send_get_acm_params(cmd_get_acm_param &acm_params);
response_type send_set_dpdi_params(dpdi_parameters &dpdi_params);
response_type send_get_dpdi_params(dpdi_parameters &dpdi_params);
response_type send_set_lbq_params(const uint32_t & tick_ms, const uint32_t & bucket_size);
response_type send_set_qos_params(const std::string &node, const name_classes_qos & class_qos);
response_type send_get_qos_params(std::string &node, const name_classes_qos & class_qos);
void set_stdout_callback(std::function<void(const char *, uint32_t)> cb);
void abort();
private:
system_client(const system_client&) = delete;
const system_client& operator=(const system_client&) = delete;
boost::asio::io_context io_context;
client clt;
std::function<void(const std::string&)> log;
std::thread ctx_thread;
std::atomic<uint32_t> cmd_id;
std::mutex cb_mtx;
std::function<void(const char *, uint32_t len)> stdout_cb;
std::mutex responses_mtx;
std::map<uint32_t, std::queue<response_type>> responses;
std::map<uint32_t, std::queue<std::any>> responses_data;
std::condition_variable cv_resp;
state current_state;
std::mutex cmd_in_progress_mtx;
response_type wait_for_response (uint32_t id, const cmd_type & type, std::any & data);
response_type wait_for_progressing_response(uint32_t c_id, const cmd_type & cmd_t);
void process_response (uint32_t id, response_type resp, std::any && data);
void data_received (const std::vector<uint8_t> & data);
void send_abort (uint32_t id);
template<typename... Args>
void send_to_socket(Args&&... args)
{
//TO-DO: need case for empty parameter pack?
std::stringstream s;
s << std::noskipws;
{
cereal::BinaryOutputArchive oarchive(s);
oarchive(std::forward<Args>(args)...);
}
std::vector<uint8_t> data((std::istream_iterator<uint8_t>(s)), std::istream_iterator<uint8_t>());
clt.write(data);
}
};

View File

@@ -1,671 +0,0 @@
#pragma once
#include <cereal/archives/binary.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
namespace fm
{
enum class side_type
{
local = 0,
tftp = 1,
config = 2
};
struct side_description
{
side_type s_type;
std::string filepath;
template<class Archive>
void serialize(Archive & archive)
{
archive(s_type, filepath);
}
};
}
enum class cmd_dma_debugg
{
start = 0,
stop = 1,
reset = 2,
reinit = 3,
modcod = 4,
log_bool = 5,
data_mode = 6,
default_params = 7,
buc_voltage = 8,
lnb_voltage = 9,
_10mhz_tx = 10,
_10mhz_rx = 11,
powerdown_plata = 12,
_10MHz_out = 13,
current_state_tx = 14,
current_state_oib = 15,
save_config = 16,
begin_save_config = 17
};
enum class name_classes_qos
{
realtime1 = 0,
realtime2,
realtime3,
critical1,
critical2,
critical3,
cir,
pir,
wcr1,
wcr2,
wcr3,
enable,
ful_node
};
enum class cmd_get_dma_debugg_enum
{
speed_tx = 0,
speed_rx = 1,
modcod = 2,
drop_bad_rx = 3,
drop_full_rx = 4,
packet_ok_rx = 5,
packet_little_tx = 6,
packet_big_tx = 7,
bad_modcod_tx = 8,
bad_length_tx = 9,
reset_cnt_rx = 10,
data_mode = 11,
buc_voltage = 12,
lnb_voltage = 13,
_10mhz_tx = 14,
_10mhz_rx = 15,
powerdown_plata = 16,
_10MHz_out = 17,
current_state_tx = 18,
real_dpdi_shift = 19,
freq_error_offset = 20,
freq_fine_estimate = 21,
speed_tx_iface = 22,
speed_rx_iface = 23,
current_state_oib = 24,
ratio_signal_signal = 25,
status_init = 26
};
enum class cmd_10g_config
{
udp_payload_lenght = 0,
local_ip = 1,
dest_ip = 2,
gateway_ip = 3,
subnet_mask = 4,
udp_source_port = 5,
udp_dest_port = 6,
local_mac_31_0 = 7,
local_mac_32_47 = 8,
mtx_chan0 = 9,
mtx_chan1 = 10
};
enum class cmd_level_dem
{
//! ОСШ на входе
snr = 0,
//! RSSI на входе
rssi = 1,
sym_sync_lock = 2,
freq_search_lock = 3,
afc_lock = 4,
pkt_sync = 5,
phase_inv = 6,
afc_error = 7,
sym_error = 8,
fine_freq_error = 9,
crs_freq_error = 10,
gc_gain_aru = 11,
rollof = 12,
carrier_lock = 13,
filt_adapt_lock = 14,
snr_acm = 15,
modcod_tx = 16
};
enum class interface_value
{
all = 0,
sat0 = 1,
gigabit = 2,
modulator = 3,
demodulator = 4
};
enum class zynq_value
{
ps_volt = 0,
pl_volt = 1,
adrv_temp = 2,
pl_temp = 3,
ps_temp = 4,
adrv_temp2 = 5
};
enum class modulator_value
{
lo_freaquency = 0,
baud_rate = 1,
temp_treshold = 2,
mod_reset = 3,
modcod = 4,
rollof = 5,
mode_transmitt = 6,
if_overload = 7,
gold_seq = 8
};
enum class demodulator_value
{
lo_freaquency = 0,
baud_rate = 1,
freq_search = 2,
demod_reset = 3,
fec_frame_size = 4,
rvt = 5,
mode_gain_control = 6,
sig_min = 7,
sig_max = 8,
afc_rst = 9,
mode_demod = 10,
gold_seq = 11,
attitude_signals = 12,
is_reset_CinC = 13
};
enum class getdemodulator_value
{
lo_freaquency = 0,
baud_rate = 1,
dummy_counter = 2,
modcod = 3,
mode_gain_control = 4,
sig_min = 5,
sig_max = 6,
rvt = 7,
fec_size = 8,
mode_demod = 9,
cnt_bad_lock_cinc = 10,
gold_seq = 11,
attitude_signals = 12,
cnt_dem_time_sinc = 13,
cnt_dem_lock_afc = 14,
cnt_dem_sync_pack = 15,
cnt_dem_sinc_frec_corse = 16,
freq_lock_estimate = 17,
freq_fine_estimate = 18,
is_reset_CinC = 19,
type_pack = 20,
is_pilots = 21
};
enum class gain_value
{
tx1 = 0,
tx2 = 1,
rx1 = 2,
rx2 = 3,
txpwd = 4,
rxpwd = 5
};
enum class acm_value
{
enable = 0,
modcod_min = 1,
modcod_max = 2,
attenuation_min = 3,
attenuatin_max = 4,
required_snr = 5
};
enum class cmd_radio
{
tx1 = 0,
tx2 = 1,
rx1 = 2,
rx2 = 3,
global = 4,
global2 = 5
};
enum class network_value
{
network = 0,
mode_interface = 1,
version = 2,
mask = 3,
gateway = 4,
dhcp_on = 5,
dhcp_range = 6,
network_data = 7,
chip_id = 8,
serial = 9,
mac_eth0 = 10,
mac_eth1 = 11,
name_serv = 12,
network_debug_send = 13,
network_port_metric = 14,
network_port_data = 15,
periodic_send_metrics = 16,
if_debug_mode = 17
};
enum class cmd_type
{
login = 0,
exit = 1,
ping = 2,
tracert = 3,
interface = 4,
copy = 5,
abort = 6,
set_demodulator_frequency = 7,
get_demodulator_frequency = 8,
set_gain_param = 9,
get_gain_param = 10,
radio_on_of = 11,
modulator_param = 12,
zynq_param = 13,
set_demodulator_param = 14,
get_demodulator_param = 15,
get_modulator_param = 16,
set_network = 17,
get_level_dmd = 18,
set_10g_config = 19,
set_dma_debugg = 20,
get_dma_debugg = 21,
set_baudrate_rollof_dmd = 22,
set_acm_params = 23,
get_acm_params = 24,
set_params_dpdi = 25,
get_params_dpdi = 26,
get_network = 27,
set_qos_settings = 28,
get_qos_settings = 29,
set_lbq_params = 30
};
struct cmd_lbq_params
{
uint32_t tick_ms;
uint32_t bucket_size;
template<class Archive>
void serialize(Archive & archive)
{
archive(tick_ms, bucket_size);
}
};
struct cmd_get_network
{
std::string network;
network_value net_val;
template<class Archive>
void serialize(Archive & archive)
{
archive(net_val, network);
}
};
struct cmd_set_network
{
std::string network;
network_value net_val;
template<class Archive>
void serialize(Archive & archive)
{
archive(net_val, network);
}
};
struct dpdi_parameters
{
uint8_t latitude_station_grad = 0;
uint8_t latitude_station_minute = 0;
uint8_t longitude_station_grad = 0;
uint8_t longitude_station_minute = 0;
uint8_t longitude_sattelite_grad = 0;
uint8_t longitude_sattelite_minute = 0;
bool is_delay_window = 0;
uint32_t max_delay = 1;
uint32_t min_delay = 0;
uint32_t freq_offset = 0;
template<class Archive>
void serialize(Archive & archive)
{
archive(latitude_station_grad, latitude_station_minute,
longitude_station_grad, longitude_station_minute,
longitude_sattelite_grad, longitude_sattelite_minute,
is_delay_window, max_delay, min_delay, freq_offset);
}
};
struct ACM_parameters_serv
{
double snr_treashold = 0;
double snr_treashold_acm = 0.5;
uint32_t period_pack = 15;
uint8_t max_modcod = 4;
uint8_t min_modcod = 4;
int max_attenuation = 0;
int min_attenuation = 0;
bool enable = false;
bool enable_auto_atten = false;
};
struct cmd_get_acm_param
{
double snr_treashold = 0;
double snr_treashold_acm = 0.5;
uint32_t period_pack = 15;
uint8_t max_modcod = 4;
uint8_t min_modcod = 4;
int max_attenuation = 0;
int min_attenuation = 0;
bool enable = false;
bool enable_auto_atten = false;
template<class Archive>
void serialize(Archive & archive)
{
archive(enable, max_attenuation, max_modcod, min_attenuation, min_modcod, snr_treashold, enable_auto_atten, snr_treashold_acm, period_pack);
}
};
struct cmd_set_qos_settings
{
std::string node;
name_classes_qos class_qos;
template<class Archive>
void serialize(Archive & archive)
{
archive(node, class_qos);
}
};
struct cmd_get_qos_settings
{
std::string node;
name_classes_qos class_qos;
template<class Archive>
void serialize(Archive & archive)
{
archive(node, class_qos);
}
};
struct cmd_set_acm_param
{
ACM_parameters_serv acm_params;
template<class Archive>
void serialize(Archive & archive)
{
archive(acm_params.enable, acm_params.max_attenuation, acm_params.max_modcod, acm_params.min_attenuation, acm_params.min_modcod, acm_params.snr_treashold, acm_params.enable_auto_atten, acm_params.snr_treashold_acm, acm_params.period_pack);
}
};
struct cmd_get_dma_debug
{
std::string value;
cmd_get_dma_debugg_enum dma_debugg;
template<class Archive>
void serialize(Archive & archive)
{
archive(dma_debugg, value);
}
};
struct cmd_set_dma_debug
{
std::string value;
cmd_dma_debugg dma_debugg;
template<class Archive>
void serialize(Archive & archive)
{
archive(dma_debugg, value);
}
};
struct cmd_set_10g_config
{
std::string value;
cmd_10g_config _10g_config;
template<class Archive>
void serialize(Archive & archive)
{
archive(_10g_config, value);
}
};
struct cmd_get_level_dem
{
double value;
cmd_level_dem lvl_dem;
template<class Archive>
void serialize(Archive & archive)
{
archive(lvl_dem, value);
}
};
struct cmd_zynq_param
{
zynq_value cmd_zynq;
template<class Archive>
void serialize(Archive & archive)
{
archive(cmd_zynq);
}
};
struct radio_enable
{
cmd_radio cmd_radio_;
bool enbl;
template<class Archive>
void serialize(Archive & archive)
{
archive(cmd_radio_, enbl);
}
};
struct cmd_set_modulator_param
{
modulator_value mod_val;
long long value;
template<class Archive>
void serialize(Archive & archive)
{
archive(mod_val,value);
}
};
struct cmd_get_modulator_param
{
modulator_value mod_val;
uint32_t value;
template<class Archive>
void serialize(Archive & archive)
{
archive(mod_val,value);
}
};
struct cmd_set_rollof_and_demod
{
double baudrate;
double rollof;
template<class Archive>
void serialize(Archive & archive)
{
archive(baudrate,rollof);
}
};
struct cmd_set_demodulator_param
{
demodulator_value demod_val;
uint32_t value;
template<class Archive>
void serialize(Archive & archive)
{
archive(demod_val,value);
}
};
struct cmd_get_demodulator_param
{
getdemodulator_value demod_val;
uint32_t value;
template<class Archive>
void serialize(Archive & archive)
{
archive(demod_val,value);
}
};
struct set_gain_par
{
gain_value g_val;
double _gain;
template<class Archive>
void serialize(Archive & archive)
{
archive(g_val,_gain);
}
};
struct get_gain_par
{
gain_value g_val;
template<class Archive>
void serialize(Archive & archive)
{
archive(g_val);
}
};
struct set_dem_freq_cmd
{
uint32_t frequency;
template<class Archive>
void serialize(Archive & archive)
{
archive(frequency);
}
};
enum class response_type
{
ok = 0,
error = 1,
in_progress = 3,
abort = 4,
busy = 5
};
enum class access_rights
{
not_allowed = 0,
user = 1,
admin = 2
};
struct cmd_header
{
uint32_t id;
cmd_type cmd;
template<class Archive>
void serialize(Archive & archive)
{
archive(id, cmd);
}
};
struct login_cmd
{
std::string username;
std::string pass;
template<class Archive>
void serialize(Archive & archive)
{
archive(username, pass);
}
};
struct ping_cmd
{
std::string ip_address;
size_t count;
template<class Archive>
void serialize(Archive & archive)
{
archive(ip_address, count);
}
};
struct tracert_cmd
{
std::string ip_address;
template<class Archive>
void serialize(Archive & archive)
{
archive(ip_address);
}
};
struct interface_cmd
{
interface_value val;
template<class Archive>
void serialize(Archive & archive)
{
archive(val);
}
};
struct copy_cmd
{
fm::side_description src;
fm::side_description dst;
template<class Archive>
void serialize(Archive & archive)
{
archive(src, dst);
}
};
struct response_header
{
uint32_t id;
cmd_type cmd;
response_type rsp;
template<class Archive>
void serialize(Archive & archive)
{
archive(id, cmd, rsp);
}
};

View File

@@ -1,23 +0,0 @@
#pragma once
#include <boost/asio.hpp>
namespace seq_packet
{
using namespace boost::asio::local;
struct seqpacket_protocol
{
int type() const { return SOCK_SEQPACKET; }
int protocol() const { return 0; }
int family() const { return AF_UNIX; }
using endpoint = basic_endpoint<seqpacket_protocol>;
using socket = boost::asio::basic_stream_socket<seqpacket_protocol>;
using acceptor = boost::asio::basic_socket_acceptor<seqpacket_protocol>;
#if !defined(BOOST_ASIO_NO_IOSTREAM)
/// The UNIX domain iostream type.
typedef boost::asio::basic_socket_iostream<seqpacket_protocol> iostream;
#endif // !defined(BOOST_ASIO_NO_IOSTREAM)
};
}

View File

@@ -1,224 +0,0 @@
#ifndef __CONTROL_PROTO_COMMANDS__
#define __CONTROL_PROTO_COMMANDS__
#include <iostream>
#include <string>
#ifdef __cplusplus
#include <cstdint>
#define EXTERNC extern "C"
#else
#include <stdint.h>
#define EXTERNC extern
#endif
typedef unsigned int TSID;
typedef enum CP_Res {
OK = 0,
TIMEOUT,
ERROR,
ABORT,
BUSY
} CP_Result;
typedef void (*CP_cmd_stdout_cb)(const char * str, uint32_t len);
/*
cb - callback for receive stdout of command
*/
EXTERNC void CP_SetCmdStdoutCallback(TSID sid, CP_cmd_stdout_cb cb);
/*
sid -- current session ID
*/
EXTERNC void CP_CmdAbort(TSID sid);
/*
host -- host name
user -- user name
pwd -- password hash
sid -- output session ID (used for all requests)
access -- output type of privilegies {admin|operator|...etc}
*/
EXTERNC CP_Result CP_Login(const char * user, const char * pwd, TSID * sid, unsigned int * access);
/*
sid -- current session ID
*/
EXTERNC CP_Result CP_Logout(TSID sid);
/*
sid -- current session ID
ip_address -- IP address of the host
packet_count -- count of packets to send
*/
EXTERNC CP_Result CP_GetDmaDebug(TSID sid, const char *command, std::string *val);
EXTERNC CP_Result CP_SetDemFreq(TSID sid, uint32_t freq);
EXTERNC CP_Result CP_GetDemFreq(TSID sid, uint32_t * freq);
EXTERNC CP_Result CP_SetDmaDebug(TSID sid, const char *command, std::string val);
EXTERNC CP_Result CP_Set10gConfig(TSID sid, const char *parameter, std::string val);
EXTERNC CP_Result CP_SetRollofBaudrate(TSID sid, double rollof,double baudrate);
//interfaces<TX1><TX2><RX1><RX2>
EXTERNC CP_Result CP_GetGain(TSID sid, const char *gain_interface, double *gain);
//interfaces<TX1><TX2><RX1><RX2>
EXTERNC CP_Result CP_SetGain(TSID sid, const char *gain_interface, double gain);
//interfaces<TX1><TX2><RX1><RX2>
EXTERNC CP_Result CP_RadioEnable(TSID sid, const char *radio_interface, bool on_of);
//interfaces<TX1><TX2><RX1><RX2>
/*
BOD -- baud_rate
SPREAD -- koef spread
LO -- lo frequency
*/
EXTERNC CP_Result CP_ModulatorParams(TSID sid, const char *modulator_param, uint32_t value);
EXTERNC CP_Result CP_GetModulatorParams(TSID sid, const char *modulator_param, uint32_t *value);
EXTERNC CP_Result CP_GetDemodulatorParams(TSID sid, const char *demodulator_param, uint32_t *value);
EXTERNC CP_Result CP_GetLevelDemod(TSID sid, const char * param ,double *value);
EXTERNC CP_Result CP_DemodulatorParams(TSID sid, const char *demodulator_param, uint32_t value);
EXTERNC CP_Result CP_SetLBQParams(TSID sid, const uint32_t &tick_ms, const uint32_t &bucket_size);
EXTERNC CP_Result CP_SetQoSParams(TSID sid, const std::string &type_node, const std::string & node);
EXTERNC CP_Result CP_GetQoSParams(TSID sid, const std::string &type_node, std::string * node);
struct ACM_parameters_serv_
{
double snr_treashold = 0;
double snr_treashold_acm = 0.5;
uint32_t period_pack = 15;
uint8_t max_modcod = 4;
uint8_t min_modcod = 4;
int max_attenuation = 0;
int min_attenuation = 0;
bool enable = false;
bool enable_auto_atten = false;
};
struct DPDI_parmeters
{
uint8_t latitude_station_grad = 0;
uint8_t latitude_station_minute = 0;
uint8_t longitude_station_grad = 0;
uint8_t longitude_station_minute = 0;
uint8_t longitude_sattelite_grad = 0;
uint8_t longitude_sattelite_minute = 0;
bool is_delay_window = 0;
uint32_t max_delay = 1;
uint32_t min_delay = 0;
uint32_t freq_offset = 0;
};
EXTERNC CP_Result CP_SetAcmParams(TSID sid, ACM_parameters_serv_ acm_params);
EXTERNC CP_Result CP_GetAcmParams(TSID sid, ACM_parameters_serv_ *acm_params);
EXTERNC CP_Result CP_SetDpdiParams(TSID sid, DPDI_parmeters dpdi_params);
EXTERNC CP_Result CP_GetDpdiParams(TSID sid, DPDI_parmeters *dpdi_pars_get);
/*
PSV
PLV
PST
PLT
ADRVT
*/
EXTERNC CP_Result CP_ZynqParams(TSID sid, const char *zynq_param, double *value);
EXTERNC CP_Result CP_SetNetwork(TSID sid, const char *param_name, const char *val);
EXTERNC CP_Result CP_GetNetwork(TSID sid, const char *param_name, std::string *val);
/*
ip_address -- new IP address fot the host
*/
EXTERNC CP_Result CP_Ping(TSID sid, const char * ip_address, int packet_count);
/*
sid -- current session ID
ip_address -- IP address of the host
*/
EXTERNC CP_Result CP_Traceroute(TSID sid, const char * ip_address);
/*
sid -- current session ID
src -- {"running-config"|"startup-config"|host_address}
dst -- {"running-config"|"startup-config"|host_address}
NOTE:
src and dst both must be different
*/
EXTERNC CP_Result CP_Copy(TSID sid, const char * src, const char * dst);
/*
sid -- current session ID
interface -- {"all"|"sat0"|"gigabit"|"modulator"|"demodulator"}
output data goes to callback
*/
EXTERNC CP_Result CP_ShowInterface(TSID sid, const char * interface);
/*
sid -- current session ID
out_startup_config -- received information about startup config
*/
EXTERNC CP_Result CP_ShowStartupConfig(TSID sid, char ** out_startup_config);
/*
sid -- current session ID
interface -- {"all"|"sat0"|"gigabit"|"modulator"|"demodulator"}
out_startup_config -- received information about startup config
*/
EXTERNC CP_Result CP_ShowStartupConfigInterface(TSID sid, const char * interface, char ** out_startup_config);
// Demodulator
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
rate -- symbol rate
*/
EXTERNC CP_Result CP_SetDemodSymrate(TSID sid, const char * interface, uint32_t rate);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
rate -- frequency value in Hertz
*/
EXTERNC CP_Result CP_SetDemodFrequency(TSID sid, const char * interface, uint32_t freq);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
mode -- {"on"|"off"|"auto"}
*/
EXTERNC CP_Result CP_SetDemodSpectrum(TSID sid, const char * interface, const char * mode);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
mode -- {"on"|"off"}
*/
EXTERNC CP_Result CP_SetDemodReference(TSID sid, const char * interface, const char * mode);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
rate -- frequency value in Hertz
*/
EXTERNC CP_Result CP_SetDemodSearch(TSID sid, const char * interface, uint32_t freq);
// Modulator
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
rate -- symbol rate
*/
EXTERNC CP_Result CP_SetModSymrate(TSID sid, const char * interface, uint32_t rate);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
rate -- frequency value in Hertz
*/
EXTERNC CP_Result CP_SetModSymFrequency(TSID sid, const char * interface, uint32_t freq);
/*
sid -- current session ID
interface -- {"SAT"|"Ethernet"|"Loopback"}
mode -- {"on"|"off"}
*/
EXTERNC CP_Result CP_SetModReference(TSID sid, const char * interface, const char * mode);
#endif

105
devtool.py Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/python3
import json
import sys
import requests
USERNAME = "developer"
PASSWORD = "fuckyou123"
def do_login(base_url):
session = requests.session()
login_data = json.dumps({
"username": USERNAME,
"password": PASSWORD
}, ensure_ascii=True, indent=0)
session.get(f"{base_url}/login")
login_result = json.loads(session.post(f"{base_url}/login", headers={'Content-Type': 'application/json'}, data=login_data).content.decode('utf-8'))
if "error" in login_result:
raise RuntimeError(f"Failed to login: {login_result}")
return session
def cp_set_dma_debug(base_url, param_name, value):
session = do_login(base_url)
res = session.post(f"{base_url}/dev/cpapicall", params={
"f": "SetDmaDebug",
"param": param_name,
"value": value
})
return res.content.decode('utf-8')
def cp_get_dma_debug(base_url, param_name):
session = do_login(base_url)
res = session.post(f"{base_url}/dev/cpapicall", params={
"f": "GetDmaDebug",
"param": param_name
})
return res.content.decode('utf-8')
def cp_set_network(base_url, param_name, value):
session = do_login(base_url)
res = session.post(f"{base_url}/dev/cpapicall", params={
"f": "SetDmaDebug",
"param": param_name,
"value": value
})
return res.content.decode('utf-8')
def cp_get_network(base_url, param_name):
session = do_login(base_url)
res = session.post(f"{base_url}/dev/cpapicall", params={
"f": "GetDmaDebug",
"param": param_name
})
return res.content.decode('utf-8')
def set_logging(base_url, value):
print(cp_set_dma_debug(base_url, "log_bool", value))
if __name__ == '__main__':
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} http(s)://terminal-url logging on|off")
print(f" set_dma_debug <param_name> <value>")
print(f" get_dma_debug <param_name>")
print(f" set_network <param_name> <value>")
print(f" get_network <param_name>")
exit(1)
if sys.argv[2] == "logging":
if len(sys.argv) != 4:
print("Wrong logging usage!")
else:
set_logging(sys.argv[1], {"on": "true", "off": "false"}[sys.argv[3]])
elif sys.argv[2] == "set_dma_debug":
if len(sys.argv) != 5:
print("Wrong set dma debug usage!")
else:
print(cp_set_dma_debug(sys.argv[1], sys.argv[3], sys.argv[4]))
elif sys.argv[2] == "get_dma_debug":
if len(sys.argv) != 4:
print("Wrong get dma debug usage!")
else:
print(cp_get_dma_debug(sys.argv[1], sys.argv[3]))
elif sys.argv[2] == "set_network":
if len(sys.argv) != 5:
print("Wrong set network usage!")
else:
print(cp_set_dma_debug(sys.argv[1], sys.argv[3], sys.argv[4]))
elif sys.argv[2] == "get_network":
if len(sys.argv) != 4:
print("Wrong get dma debug usage!")
else:
print(cp_get_dma_debug(sys.argv[1], sys.argv[3]))
else:
print(f"Unknown action: {sys.argv[1]}")

15
docs.md Normal file
View File

@@ -0,0 +1,15 @@
# Документация
## HTTP(s) Запросы
В этом разделе будет документация на запросы сервера и ответы сервера.
### Статические файлы
Такие расположены по следующим адресам (веб адрес - путь в файловой системе):
* `/favicon.ico` - `static/favicon.png`
* `/js/vue.js` - `static/vue.js`
* `/style.css` - `static/style.css`
На эти ресурсы можно ходить только запросом `GET`. Любой другой

View File

@@ -0,0 +1,491 @@
{
"modem_types": {
"tdma": {
"modem_name": "VSAT Модем",
"dangerousParamGroups": {
"buclnb": "Применение неправильных настроек может вывести из строя оборудование! Продолжить?",
"network": "Применение этих настроек может сделать модем недоступным! Продолжить?"
},
"params": {
"rxtx": [
{"widget": "h2", "label": "Настройки приема/передачи"},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки передатчика"},
{"widget": "checkbox", "label": "Включить передатчик", "name": "txEn"},
{
"widget": "select", "label": "Режим работы модулятора", "name": "txModulatorIsTest",
"values": [{"label": "Нормальный", "value": "false"}, {"label": "Тест (CW)", "value": "true"}]
},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "txCentralFreq", "min": 900000, "step": 0.01, "v_show": "paramRxtx.txModulatorIsTest"},
{"widget": "number", "label": "Ослабление, дБ", "name": "txAttenuation", "max": 0, "min": -90, "step": 1},
{"widget": "number", "label": "Ограничение ослабления", "name": "txAttenuationLimit", "max": 0, "min": -40, "step": 0.25}
]
},
{
"widget": "settings-container",
"childs": [
{ "widget": "h3", "label": "Настройки приемника" },
{
"widget": "select", "label": "Режим управления усилением", "name": "rxAgcEn",
"values": [
{"label": "АРУ", "value": "true"},
{"label": "РРУ", "value": "false"}
]
},
{"widget": "number", "label": "Ручное усиление, дБ", "name": "rxManualGain", "v_show": "!paramRxtx.rxAgcEn", "min": -40},
{"widget": "checkbox", "label": "Инверсия спектра", "name": "rxSpectrumInversion"},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "rxCentralFreq", "min": 900000, "step": 0.01},
{"widget": "number-int", "label": "Символьная скорость, Бод", "name": "rxBaudrate", "min": 200000, "max": 54000000, "step": 1},
{
"widget": "select", "label": "Roll-off", "name": "rxRolloff",
"values": [{"label": "0.02", "value": "20"}, {"label": "0.05", "value": "50"}, {"label": "0.10", "value": "100"}, {"label": "0.15", "value": "150"}, {"label": "0.20", "value": "200"}, {"label": "0.25", "value": "250"}]
}
]
}
]
}
],
"buclnb": [
{"widget": "h2", "label": "Настройки питания и опорного генератора"},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки BUC"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "bucRefClk10M"},
{
"widget": "select", "label": "Питание BUC", "name": "bucPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "24В", "value": "24"}
]
},
{"widget": "number-int", "label": "Частота LO, кГц", "name": "bucLoKhz", "min": 0, "max": 40000000, "step": 1}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки LNB"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "lnbRefClk10M"},
{
"widget": "select", "label": "Питание LNB", "name": "lnbPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "13В", "value": "13"},
{"label": "18В", "value": "18"},
{"label": "24В", "value": "24"}
]
},
{"widget": "number-int", "label": "Частота LO, кГц", "name": "lnbLoKhz", "min": 0, "max": 40000000, "step": 1}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Сервисные настройки"},
{"widget": "checkbox", "label": "Подача опоры 10МГц на 'Выход 10МГц'", "name": "srvRefClk10M"},
{"widget": "checkbox", "label": "Автозапуск BUC и LNB при включении", "name": "bucLnbAutoStart"}
]
}
]
}
],
"dpdi": [
{"widget": "h2", "label": "Настройки DPDI"},
{
"widget": "settings-container",
"childs": [
{
"widget": "select", "label": "Метод расчета задержки", "name": "isPositional",
"values": [
{"label": "Позиционированием", "value": "true"},
{"label": "Окном задержки", "value": "false"}
]
},
{"widget": "h2", "label": "Настройки позиционирования", "v_show": "paramDpdi.isPositional === true"},
{"widget": "number", "label": "Широта станции", "name": "positionStationLatitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "number", "label": "Долгота станции", "name": "positionStationLongitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "number", "label": "Подспутниковая точка", "name": "positionSatelliteLongitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "number", "label": "Задержка до спутника, мс", "name": "delay", "v_show": "paramDpdi.isPositional === false", "min": 0, "step": 0.1, "max": 400}]
},
{"widget": "submit"}
],
"network": [
{"widget": "h2", "label": "Настройки сети"},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки интерфейса управления"},
{"widget": "ip-address-mask", "label": "Интерфейс управления (a.d.d.r/mask)", "name": "managementIp"},
{"widget": "text", "label": "Имя веб-сервера", "name": "serverName"}
]
}
]
},
"tabs": [
{"name": "monitoring", "desc": "Мониторинг"},
{"name": "setup", "desc": "Настройки"},
{"name": "admin", "desc": "Администрирование"}
]
},
"scpc": {
"modem_name": "RCSM-101",
"dangerousParamGroups": {
"buclnb": "Применение неправильных настроек может вывести из строя оборудование! Продолжить?",
"network": "Применение этих настроек может сделать модем недоступным! Продолжить?"
},
"params": {
"rxtx": [
{"widget": "h2", "label": "Настройки приема/передачи"},
{
"widget": "settings-container",
"childs": [
{
"widget": "select", "label": "Режим работы", "name": "isCinC",
"values": [{"label": "SCPC", "value": "false"}, {"label": "CinC", "value": "true"}]
}
]
},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки передатчика"},
{"widget": "checkbox", "label": "Включить передатчик", "name": "txEn"},
{"widget": "checkbox", "label": "Автоматический запуск передатчика", "name": "txAutoStart"},
{
"widget": "select", "label": "Режим работы модулятора", "name": "txModulatorIsTest",
"values": [{"label": "Нормальный", "value": "false"}, {"label": "Тест (CW)", "value": "true"}]
},
{
"widget": "select", "label": "Входные данные", "name": "txIsTestInput",
"values": [{"label": "Ethernet", "value": "false"}, {"label": "Тест", "value": "true"}]
},
{"widget": "h3", "label": "Параметры передачи"},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "txCentralFreq", "min": 100000, "max": 6000000, "step": 0.01},
{"widget": "number-int", "label": "Символьная скорость, Бод", "name": "txBaudrate", "min": 200000, "max": 54000000},
{
"widget": "select", "label": "Roll-off", "name": "txRolloff",
"values": [{"label": "0.02", "value": "20"}, {"label": "0.05", "value": "50"}, {"label": "0.10", "value": "100"}, {"label": "0.15", "value": "150"}, {"label": "0.20", "value": "200"}, {"label": "0.25", "value": "250"}]
},
{
"widget": "select", "label": "Номер последовательности Голда", "name": "txGoldan",
"values": [{"label": "0", "value": "0"}, {"label": "1", "value": "1"}]
},
{"widget": "number", "label": "Ослабление, дБ", "name": "txAttenuation", "max": 0, "min": -40, "step": 0.25}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Режим работы DVB-S2"},
{"widget": "number", "label": "Период служебных пакетов, сек", "name": "dvbServicePacketPeriod", "min": 0, "step": 1, "max": 60},
{
"widget": "select", "label": "Режим модуляции", "name": "dvbIsAcm",
"values": [{"label": "CCM", "value": "false"}, {"label": "ACM", "value": "true"}]
},
{
"widget": "select", "label": "Размер кадра", "name": "txFrameSizeNormal",
"values": [{"label": "normal", "value": "true"}, {"label": "short", "value": "false"}]
},
{"widget": "checkbox", "label": "Пилот-символы", "name": "txIsPilots"},
{"widget": "modulation-modcod", "label": "Модуляция", "name": "dvbCcm", "v_show": "paramRxtx.dvbIsAcm === false"},
{"widget": "modulation-speed", "label": "Скорость кода", "name": "dvbCcm", "v_show": "paramRxtx.dvbIsAcm === false"},
{"widget": "watch-expr", "label": "Расчетная скорость", "expr": "calcInterfaceSpeedKb(paramRxtx.txBaudrate, paramRxtx.dvbCcmModulation, paramRxtx.dvbCcmSpeed, paramRxtx.txFrameSizeNormal)", "v_show": "paramRxtx.dvbIsAcm === false"},
{"widget": "watch-expr", "label": "Текущий модкод", "expr": "statTx.modcod", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "modulation-modcod", "label": "Модуляция (мин. режим)", "name": "dvbAcmMin", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "modulation-speed", "label": "Скорость кода (мин. режим)", "name": "dvbAcmMin", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "watch-expr", "label": "Расчетная скорость (мин. режим)", "expr": "calcInterfaceSpeedKb(paramRxtx.txBaudrate, paramRxtx.dvbAcmMinModulation, paramRxtx.dvbAcmMinSpeed, paramRxtx.txFrameSizeNormal)", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "modulation-modcod", "label": "Модуляция (макс. режим)", "name": "dvbAcmMax", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "modulation-speed", "label": "Скорость кода (макс. режим)", "name": "dvbAcmMax", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "watch-expr", "label": "Расчетная скорость (макс. режим)", "expr": "calcInterfaceSpeedKb(paramRxtx.txBaudrate, paramRxtx.dvbAcmMaxModulation, paramRxtx.dvbAcmMaxSpeed, paramRxtx.txFrameSizeNormal)", "v_show": "paramRxtx.dvbIsAcm === true"},
{"widget": "number", "label": "Запас ОСШ, дБ", "name": "dvbSnrReserve", "min": 0, "step": 0.01, "max": 10}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Авто-регулировка мощности"},
{"widget": "checkbox", "label": "Авто-регулировка мощности", "name": "aupcEn"},
{"widget": "number", "label": "Минимальное ослабление, дБ", "name": "aupcMinAttenuation", "min": 0, "step": 0.1, "max": 10},
{"widget": "number", "label": "Максимальное ослабление, дБ", "name": "aupcMaxAttenuation", "min": 0, "step": 0.1, "max": 10},
{"widget": "number", "label": "Требуемое ОСШ", "name": "aupcRequiredSnr", "min": 0, "step": 0.01, "max": 30}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки приемника"},
{
"widget": "select", "label": "Режим управления усилением", "name": "rxAgcEn",
"values": [{"label": "РРУ", "value": "false"}, {"label": "АРУ", "value": "true"}]
},
{"widget": "number", "label": "Усиление, дБ", "name": "rxManualGain", "min": -40, "step": 0.01, "max": 40, "v_show": "paramRxtx.rxAgcEn === false"},
{"widget": "watch-expr", "label": "Текущее усиление", "expr": "paramRxtx.rxManualGain", "v_show": "paramRxtx.rxAgcEn === true"},
{"widget": "checkbox", "label": "Инверсия спектра", "name": "rxSpectrumInversion"},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "rxCentralFreq", "min": 100000, "max": 6000000, "step": 0.01},
{"widget": "number-int", "label": "Символьная скорость, Бод", "name": "rxBaudrate", "min": 200000, "max": 54000000},
{
"widget": "select", "label": "Roll-off", "name": "rxRolloff",
"values": [{"label": "0.02", "value": "20"}, {"label": "0.05", "value": "50"}, {"label": "0.10", "value": "100"}, {"label": "0.15", "value": "150"}, {"label": "0.20", "value": "200"}, {"label": "0.25", "value": "250"}]
},
{
"widget": "select", "label": "Номер последовательности Голда", "name": "rxGoldan",
"values": [{"label": "0", "value": "0"}, {"label": "1", "value": "1"}]
}
]
}
]
}
],
"dpdi": [
{"widget": "h2", "label": "Настройки режима CinC", "v_show": "paramRxtx.isCinC"},
{
"widget": "settings-container", "v_show": "paramRxtx.isCinC",
"childs": [
{
"widget": "select", "label": "Метод расчета задержки", "name": "isPositional",
"values": [
{"label": "Позиционированием", "value": "true"},
{"label": "Окном задержки", "value": "false"}
]
},
{"widget": "number", "label": "Полоса поиска, КГц ±", "name": "searchBandwidth", "min": 0, "step": 1, "max": 100},
{"widget": "h2", "label": "Настройки позиционирования", "v_show": "paramDpdi.isPositional === true"},
{"widget": "number", "label": "Широта станции", "name": "positionStationLatitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "number", "label": "Долгота станции", "name": "positionStationLongitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "number", "label": "Подспутниковая точка", "name": "positionSatelliteLongitude", "v_show": "paramDpdi.isPositional === true", "min": -180, "step": 0.000001, "max": 180},
{"widget": "h2", "label": "Задержка до спутника", "v_show": "paramDpdi.isPositional === false"},
{"widget": "number", "label": "от, мс", "name": "delayMin", "v_show": "paramDpdi.isPositional === false", "min": 0, "step": 0.1, "max": 400},
{"widget": "number", "label": "до, мс", "name": "delayMax", "v_show": "paramDpdi.isPositional === false", "min": 0, "step": 0.1, "max": 400}]
},
{"widget": "submit", "v_show": "paramRxtx.isCinC"}
],
"buclnb": [
{"widget": "h2", "label": "Настройки питания и опорного генератора"},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки BUC"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "bucRefClk10M"},
{
"widget": "select", "label": "Питание BUC", "name": "bucPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "24В", "value": "24"},
{"label": "48В", "value": "48"}
]
}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки LNB"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "lnbRefClk10M"},
{
"widget": "select", "label": "Питание LNB", "name": "lnbPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "13В", "value": "13"},
{"label": "18В", "value": "18"},
{"label": "24В", "value": "24"}
]
}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Сервисные настройки"},
{"widget": "checkbox", "label": "Подача опоры 10МГц на 'Выход 10МГц'", "name": "srvRefClk10M"},
{"widget": "checkbox", "label": "Автозапуск BUC и LNB при включении", "name": "bucLnbAutoStart"}
]
}
]
}
],
"network": [
{"widget": "h2", "label": "Настройки сети"},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки интерфейса управления"},
{"widget": "ip-address-mask", "label": "Интерфейс управления (a.d.d.r/mask)", "name": "managementIp"},
{
"widget": "select", "label": "Режим сети", "name": "isL2",
"values": [{"label": "Маршрутизатор", "value": "false"}, {"label": "Коммутатор", "value": "true"}]
},
{"widget": "ip-address", "label": "Интерфейс данных (/24)", "name": "dataIp", "v_show": "paramNetwork.isL2 === false"},
{"widget": "number", "label": "MTU интерфейса данных", "name": "dataMtu", "min": 1500, "step": 1, "max": 2000},
{"widget": "text", "label": "Имя веб-сервера", "name": "serverName"}
]
}
]
},
"tabs": [
{"name": "monitoring", "desc": "Мониторинг"},
{"name": "setup", "desc": "Настройки"},
{"name": "qos", "desc": "QoS"},
{"name": "admin", "desc": "Администрирование"}
]
},
"shps": {
"modem_name": "ШПС Модем",
"dangerousParamGroups": {
"buclnb": "Применение неправильных настроек может вывести из строя оборудование! Продолжить?",
"network": "Применение этих настроек может сделать модем недоступным! Продолжить?"
},
"params": {
"rxtx": [
{"widget": "h2", "label": "Настройки приема/передачи"},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки передатчика"},
{"widget": "checkbox", "label": "Включить передатчик", "name": "txEn"},
{"widget": "checkbox", "label": "Автоматический запуск передатчика", "name": "txAutoStart"},
{
"widget": "select", "label": "Режим работы модулятора", "name": "txModulatorIsTest",
"values": [{"label": "Нормальный", "value": "false"}, {"label": "Тест (CW)", "value": "true"}]
},
{"widget": "number", "label": "Ослабление, дБ", "name": "txAttenuation", "max": 0, "min": -40, "step": 0.25},
{
"widget": "select", "label": "Входные данные", "name": "txIsTestInput",
"values": [{"label": "Ethernet", "value": "false"}, {"label": "Тест", "value": "true"}]
},
{"widget": "h3", "label": "Параметры передачи"},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "txCentralFreq", "min": 70000, "max": 6000000, "step": 100},
{"widget": "number-int", "label": "Символьная скорость, Бод", "name": "txBaudrate", "min": 128000, "max": 30000000},
{
"widget": "select", "label": "Roll-off", "name": "txRolloff",
"values": [{"label": "0.02", "value": "20"}, {"label": "0.05", "value": "50"}, {"label": "0.10", "value": "100"}, {"label": "0.15", "value": "150"}, {"label": "0.20", "value": "200"}, {"label": "0.25", "value": "250"}, {"label": "0.30", "value": "300"}, {"label": "0.35", "value": "350"}]
},
{"widget": "number", "label": "Коэф. расширения", "name": "txSpreadCoef", "max": 1024, "min": 8, "step": 2},
{"widget": "number", "label": "Кол-во пакетов на преамбулу", "name": "txFieldsDataPreamble", "max": 255, "min": 1, "step": 1}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Авто-регулировка мощности"},
{"widget": "checkbox", "label": "Авто-регулировка мощности", "name": "aupcEn"},
{"widget": "number", "label": "Минимальное ослабление, дБ", "name": "aupcMinAttenuation", "min": 0, "step": 0.1, "max": 10},
{"widget": "number", "label": "Максимальное ослабление, дБ", "name": "aupcMaxAttenuation", "min": 0, "step": 0.1, "max": 10},
{"widget": "number", "label": "Требуемое ОСШ", "name": "aupcRequiredSnr", "min": 0, "step": 0.01, "max": 30}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки приемника"},
{
"widget": "select", "label": "Режим управления усилением", "name": "rxAgcEn",
"values": [{"label": "РРУ", "value": "false"}, {"label": "АРУ", "value": "true"}]
},
{"widget": "number", "label": "Усиление, дБ", "name": "rxManualGain", "min": -40, "step": 0.01, "max": 40, "v_show": "paramRxtx.rxAgcEn === false"},
{"widget": "watch-expr", "label": "Текущее усиление", "expr": "paramRxtx.rxManualGain", "v_show": "paramRxtx.rxAgcEn === true"},
{"widget": "checkbox", "label": "Инверсия спектра", "name": "rxSpectrumInversion"},
{"widget": "number-int", "label": "Центральная частота, КГц", "name": "rxCentralFreq", "min": 70000, "max": 6000000, "step": 100},
{"widget": "number-int", "label": "Символьная скорость, Бод", "name": "rxBaudrate", "min": 128000, "max": 30000000},
{
"widget": "select", "label": "Roll-off", "name": "rxRolloff",
"values": [{"label": "0.02", "value": "20"}, {"label": "0.05", "value": "50"}, {"label": "0.10", "value": "100"}, {"label": "0.15", "value": "150"}, {"label": "0.20", "value": "200"}, {"label": "0.25", "value": "250"}, {"label": "0.30", "value": "300"}, {"label": "0.35", "value": "350"}]
},
{"widget": "number", "label": "Коэф. расширения", "name": "rxSpreadCoef", "max": 1024, "min": 8, "step": 2},
{"widget": "number", "label": "Порог коррелятора", "name": "rxFftShift", "max": 10, "min": 1, "step": 0.125},
{"widget": "number", "label": "Кол-во пакетов на преамбулу", "name": "rxFieldsDataPreamble", "max": 255, "min": 1, "step": 1}
]
}
]
}
],
"buclnb": [
{"widget": "h2", "label": "Настройки питания и опорного генератора"},
{
"widget": "flex-container",
"childs": [
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки BUC"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "bucRefClk10M"},
{
"widget": "select", "label": "Питание BUC", "name": "bucPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "24В", "value": "24"},
{"label": "48В", "value": "48"}
]
}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки LNB"},
{"widget": "checkbox", "label": "Подача опоры 10МГц", "name": "lnbRefClk10M"},
{
"widget": "select", "label": "Питание LNB", "name": "lnbPowering",
"values": [
{"label": "Выкл", "value": "0"},
{"label": "13В", "value": "13"},
{"label": "18В", "value": "18"},
{"label": "24В", "value": "24"}
]
}
]
},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Сервисные настройки"},
{"widget": "checkbox", "label": "Подача опоры 10МГц на 'Выход 10МГц'", "name": "srvRefClk10M"},
{"widget": "checkbox", "label": "Автозапуск BUC и LNB при включении", "name": "bucLnbAutoStart"}
]
}
]
}
],
"network": [
{"widget": "h2", "label": "Настройки сети"},
{
"widget": "settings-container",
"childs": [
{"widget": "h3", "label": "Настройки интерфейса управления"},
{"widget": "ip-address-mask", "label": "Интерфейс управления (a.d.d.r/mask)", "name": "managementIp"},
{
"widget": "select", "label": "Режим сети", "name": "isL2",
"values": [{"label": "Маршрутизатор", "value": "false"}, {"label": "Коммутатор", "value": "true"}]
},
{"widget": "ip-address", "label": "Интерфейс данных (/24)", "name": "dataIp", "v_show": "paramNetwork.isL2 === false"},
{"widget": "number", "label": "MTU интерфейса данных", "name": "dataMtu", "min": 1500, "step": 1, "max": 2000},
{"widget": "text", "label": "Имя веб-сервера", "name": "serverName"}
]
}
]
},
"tabs": [
{"name": "monitoring", "desc": "Мониторинг"},
{"name": "setup", "desc": "Настройки"},
{"name": "admin", "desc": "Администрирование"}
]
}
}
}

116
front-generator/render.py Normal file
View File

@@ -0,0 +1,116 @@
import json
from jinja2 import Environment, FileSystemLoader
import sys
import os
with open('render-params.json') as f:
GLOBAL_CONFIG = json.load(f)
def extract_param_names(mc):
result = []
def helper_extract(widget):
if 'childs' in widget:
r = []
for child in widget['childs']:
r += helper_extract(child)
return r
elif 'name' in widget:
copy_fields = {"widget": widget['widget'], "name": widget['name']}
if 'min' in widget:
copy_fields['min'] = widget['min']
if 'max' in widget:
copy_fields['max'] = widget['max']
if 'step' in widget:
copy_fields['step'] = widget['step']
match widget['widget']:
case 'select': return [{"initValue": widget['values'][0]['value']} | copy_fields]
case 'checkbox': return [{"initValue": 'false'} | copy_fields]
case 'number': return [{"initValue": widget['min'] if widget['min'] else '0'} | copy_fields]
case 'number-int': return [{"initValue": "0"} | copy_fields]
case 'modulation-modcod': return [copy_fields | {"name": widget['name'] + "Modulation", "initValue": '"QPSK"'}]
case 'modulation-speed': return [copy_fields | {"name": widget['name'] + "Speed", "initValue": '"1/4"'}]
case 'watch': return []
return [{"initValue": 'null'} | copy_fields]
return []
for cat in mc['params']:
ws = []
for w in mc['params'][cat]:
ws += helper_extract(w)
# ws.sort(key=lambda k: k['name'])
result.append({
"group": cat,
"params": ws
})
return result
def add_submit_widgets(params):
def find_submit(w):
if w['widget'] == 'submit':
return True
if 'childs' in w:
for c in w['childs']:
if find_submit(c):
return True
return False
for group in params:
wid_found = False
for wid in params[group]:
if find_submit(wid):
wid_found = True
break
if wid_found:
continue
params[group].append({"widget": "submit"})
def extract_param_groups(mc):
return [k for k in mc['params']]
def build_modem_env(modem):
if modem not in GLOBAL_CONFIG['modem_types']:
raise RuntimeError(f"Modem '{modem}' is not exist in config!")
mc = GLOBAL_CONFIG['modem_types'][modem]
add_submit_widgets(mc['params'])
return {
"modem": modem,
"modem_name": mc['modem_name'],
"header_tabs": mc['tabs'],
"tab_names_array": [t['name'] for t in mc['tabs']],
"params": mc["params"],
"dangerousParamGroups": mc["dangerousParamGroups"] if 'dangerousParamGroups' in mc else {},
"paramGroups": extract_param_names(mc),
"paramGroupsList": extract_param_groups(mc),
}
def render_modem(modem):
loader = FileSystemLoader('template')
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template('main.html')
context = build_modem_env(modem)
with open(f"main-{modem}.html", "w") as f:
f.write(template.render(context))
if __name__ == '__main__':
for mt in GLOBAL_CONFIG['modem_types']:
print(f'Generating {mt} modem...')
render_modem(mt)
os.system(f'cp -u main-{mt}.html ../static')

View File

@@ -0,0 +1,217 @@
async settingsUploadUpdate() {
if (!this.uploadFw.filename) {
alert('Выберите файл для загрузки');
return;
}
async function readFileAsArrayBuffer(fileName) {
return new Promise((resolve, reject) => {
if (!fileName) { reject(`Файл не выбран`); return }
const reader = new FileReader();
reader.onload = (e) => { resolve(reader.result) }
reader.onerror = (e) => { reject(e) }
reader.readAsArrayBuffer(fileName)
})
}
try {
this.submitStatus.firmwareUpload = true
this.uploadFw.progress = 0
const blob = await readFileAsArrayBuffer(this.uploadFw.filename)
const xhr = new XMLHttpRequest();
await new Promise((resolve) => {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
this.uploadFw.progress = Math.round((event.loaded / event.total) * 1000) / 10;
}
});
xhr.addEventListener("loadend", () => {
this.uploadFw.progress = 100
const rep = JSON.parse(xhr.responseText);
this.uploadFw.sha256 = rep['sha256']
resolve(xhr.readyState === 4 && xhr.status === 200);
});
xhr.open("PUT", "/api/firmwareUpdate", true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(blob);
});
} catch (e) {
alert(`Ошибка загрузки файла: ${e}`);
}
this.submitStatus.firmwareUpload = false
},
async settingsPerformFirmwareUpgrade() {
if (this.submitStatus.firmwareUpgrade) { return }
this.submitStatus.firmwareUpgrade = true
try {
await fetch('/api/doFirmwareUpgrade', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgrade = false
},
{% if modem == 'tdma' %}
async settingsPerformFirmwareUpgradeOta() {
if (this.submitStatus.firmwareUpgradeOta) { return }
this.submitStatus.firmwareUpgradeOta = true
try {
await fetch('/api/doFirmwareUpgrade?ota=1', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgradeOta = false
},
async settingsPerformSetCesPassword() {
if (this.submitStatus.cesPassword) { return }
this.submitStatus.cesPassword = true
try {
await fetch('/api/set/cesPassword', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'password': this.cesPasswordValue})
})
} catch (e) {
console.log("failed to perform set CES password: ", e)
}
this.submitStatus.cesPassword = false
},
{% endif %}
doModemReboot() {
if (this.submitStatus.modemReboot !== null) {
return
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'dpdi', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
},

View File

@@ -0,0 +1,52 @@
{% from 'common/widgets.j2' import build_widget %}
<div class="tabs-body-item" v-if="activeTab === 'admin' && settingFetchComplete">
{% if 'network' in params %}
{% for w in params['network'] %}{{ build_widget('network', w) | indent(12, true) }}{% endfor %}
{% endif %}
{% raw %}
<h2>Система</h2>
<div class="settings-set-container statistics-container">
<table>
<tbody>
<tr><th>Версия ПО</th><td>{{ about.firmwareVersion }}</td></tr>
<tr><th>ID модема</th><td>{{ about.modemUid }}</td></tr>
<tr><th>Серийный номер</th><td>{{ about.modemSn }}</td></tr>
<tr><th>MAC интерфейса управления</th><td>{{ about.macManagement }}</td></tr>
<tr><th>MAC интерфейса данных</th><td>{{ about.macData }}</td></tr>
</tbody>
</table>
<div>
<button class="dangerous-button" @click="doModemReboot()">Перезагрузить модем <span class="submit-spinner" v-show="submitStatus.modemReboot !== null"></span></button>
</div>
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>{% endraw %}{% if modem == 'tdma' %}
<h2>Вход в сеть ЦЗС</h2>
<div class="settings-set-container statistics-container">
<label>
<span>Хеш-строка пароля (выдается оператором NMS)</span>
<input v-model="cesPasswordValue" type="text">
</label>
<button class="action-button" @click="settingsPerformSetCesPassword()">Установить пароль<span class="submit-spinner" v-show="submitStatus.cesPassword"></span></button>
</div>{% endif %}{% raw %}
<h2>Обновление ПО</h2>
<div class="settings-set-container statistics-container">
<h3>Ручное обновление</h3>
<label>
<span>Файл {{ this.uploadFw.progress !== null ? `(${this.uploadFw.progress}%)` : '' }}</span>
<input type="file" accept="application/zip" @change="(e) => { this.uploadFw.filename = e.target.files[0] }">
<span v-if="uploadFw.sha256 !== null">SHA256: {{ uploadFw.sha256 }}</span>
</label>
<button class="action-button" @click="settingsUploadUpdate()">Загрузить<span class="submit-spinner" v-show="submitStatus.firmwareUpload"></span></button>
<button class="dangerous-button" v-show="uploadFw.sha256 !== null" @click="settingsPerformFirmwareUpgrade()">Обновить встроенное ПО<span class="submit-spinner" v-show="submitStatus.firmwareUpgrade"></span></button>
{% endraw %}{% if modem == 'tdma' %}
<h3 v-show="statDevice.upgradePercent >= 100">Обновление "по воздуху"</h3>
<button class="dangerous-button" v-show="statDevice.upgradePercent >= 100" @click="settingsPerformFirmwareUpgradeOta()">Обновить встроенное ПО<span class="submit-spinner" v-show="submitStatus.firmwareUpgradeOta"></span></button>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,7 @@
{% for g in paramGroups %}
param{{ g['group'] | title }}: {
{% for p in g['params'] %}
{{ p['name'] }}: {{ p['initValue'] }},
{% endfor %}
},
{% endfor %}

View File

@@ -0,0 +1,30 @@
{% from 'common/widgets.j2' import build_getter_js, build_setter_js %}
{% for g in paramGroups %}
settingsSubmit{{ g['group'] | title }}() {
if (this.submitStatus.{{ g['group'] }}) { return }
{% if g['group'] in dangerousParamGroups %}
{ if (!confirm("{{ dangerousParamGroups[g['group']] }}")) return }
{% endif %}
let query = {
{% for p in g['params'] %}
"{{ p['name'] }}": {{ build_getter_js(g['group'], p) }},
{% endfor %}
}
this.submitStatus.{{ g['group'] }} = true
fetch('/api/set/{{ g["group"] }}', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.update{{ g['group'] | title }}Settings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.{{ g['group'] }} = false })
},
{% endfor %}
{% for g in paramGroups %}
update{{ g['group'] | title }}Settings(vals) {
this.submitStatus.{{ g['group'] }} = false
{% for p in g['params'] %}
{{ build_setter_js(g['group'], p, "vals[\"settings\"][\"" ~ g['group'] ~ "\"][\"" ~ p['name'] ~ "\"]") }}
{% endfor %}
},
{% endfor %}

View File

@@ -0,0 +1,50 @@
statRx: {
// индикаторы
state: '?', // общее состояние
sym_sync_lock: '?', // захват символьной
freq_search_lock: '?', // Захват поиска по частоте
afc_lock: '?', // захват ФАПЧ
pkt_sync: '?', // захват пакетной синхронизации
// куча других параметров, идет в том же порядке, что и в таблице
snr: '?', rssi: '?',
modcod: '?', frameSizeNormal: '?',
isPilots: '?',
symError: '?',
freqErr: '?', freqErrAcc: '?',
inputSignalLevel: '?',
pllError: '?',
speedOnRxKbit: '?',
speedOnIifKbit: '?',
// статистика пакетов
packetsOk: '?', packetsBad: '?', packetsDummy: '?',
},
statTx: {
// состояние
state: '?',
// прочие поля
{% if modem == 'scpc' %}
snr: '?', modcod: '?', frameSizeNormal: '?', isPilots: '?', speedOnTxKbit: '?', speedOnIifKbit: '?'
{% else %}
modcod: '?', speedOnTxKbit: '?', speedOnIifKbit: '?', centerFreq: '?', symSpeed: '?'
{% endif %}
},
{% if modem == 'scpc' %}
statCinc: {
occ: '?',
correlator: null,
correlatorFails: '?',
freqErr: '?', freqErrAcc: '?',
channelDelay: '?'
},
{% endif %}
statDevice: { // температурные датчики
adrv: 0, zynq: 0, fpga: 0
{% if modem == 'tdma' %},
upgradeStatus: "", upgradePercent: 0, upgradeImage: ""
{% endif %}
},
statOs: {uptime: '?', load1: '?', load5: '?', load15: '?', totalram: '?', freeram: '?'},

View File

@@ -0,0 +1,110 @@
updateStatistics(vals) {
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
const modcods = [
"DUMMY",
"QPSK 1/4", "QPSK 1/3", "QPSK 2/5", "QPSK 1/2", "QPSK 3/5", "QPSK 2/3", "QPSK 3/4", "QPSK 4/5", "QPSK 5/6", "QPSK 8/9", "QPSK 9/10",
"8PSK 3/5", "8PSK 2/3", "8PSK 3/4", "8PSK 5/6", "8PSK 8/9", "8PSK 9/10",
"16APSK 2/3", "16APSK 3/4", "16APSK 4/5", "16APSK 5/6", "16APSK 8/9", "16APSK 9/10",
"32APSK 3/4", "32APSK 4/5", "32APSK 5/6", "32APSK 8/9", "32APSK 9/10",
]
if (typeof modcod != "number") {
return "?";
}
if (modcod < 0 || modcod >= modcods.length) {
return `? (${modcod})`
}
return modcods[modcod]
}
this.lastUpdateTime = new Date();
this.initState = vals["state"]["initState"]
this.testState = vals["state"]["testState"]
{% if modem == 'scpc' %}
this.isCinC = vals["state"]["isCinC"]
{% endif %}
this.statRx.state = vals["state"]["rx"]["state"]
this.statRx.sym_sync_lock = vals["state"]["rx"]["sym_sync_lock"]
this.statRx.freq_search_lock = vals["state"]["rx"]["freq_search_lock"]
this.statRx.afc_lock = vals["state"]["rx"]["afc_lock"]
this.statRx.pkt_sync = vals["state"]["rx"]["pkt_sync"]
this.statRx.snr = Math.round(vals["state"]["rx"]["snr"] * 10) / 10
this.statRx.rssi = Math.round(vals["state"]["rx"]["rssi"] * 10) / 10
this.statRx.modcod = modcodToStr(vals["state"]["rx"]["modcod"])
this.statRx.frameSizeNormal = vals["state"]["rx"]["frameSizeNormal"]
this.statRx.isPilots = vals["state"]["rx"]["isPilots"]
this.statRx.symError = vals["state"]["rx"]["symError"]
this.statRx.freqErr = Math.round(vals["state"]["rx"]["freqErr"] * 100) / 100
this.statRx.freqErrAcc = Math.round(vals["state"]["rx"]["freqErrAcc"] * 100) / 100
this.statRx.inputSignalLevel = vals["state"]["rx"]["inputSignalLevel"]
this.statRx.pllError = Math.round(vals["state"]["rx"]["pllError"] * 100) / 100
this.statRx.speedOnRxKbit = Math.round(vals["state"]["rx"]["speedOnRxKbit"] * 100) / 100
this.statRx.speedOnIifKbit = Math.round(vals["state"]["rx"]["speedOnIifKbit"] * 100) / 100
this.statRx.packetsOk = vals["state"]["rx"]["packetsOk"]
this.statRx.packetsBad = vals["state"]["rx"]["packetsBad"]
this.statRx.packetsDummy = vals["state"]["rx"]["packetsDummy"]
{% if modem == 'scpc' %}
this.statTx.state = vals["state"]["tx"]["state"]
this.statTx.snr = Math.round(vals["state"]["tx"]["snr"] * 100) / 100
this.statTx.modcod = modcodToStr(vals["state"]["tx"]["modcod"])
this.statTx.frameSizeNormal = vals["state"]["tx"]["frameSizeNormal"]
this.statTx.isPilots = vals["state"]["tx"]["isPilots"]
this.statTx.speedOnTxKbit = Math.round(vals["state"]["tx"]["speedOnTxKbit"] * 100) / 100
this.statTx.speedOnIifKbit = Math.round(vals["state"]["tx"]["speedOnIifKbit"] * 100) / 100
this.statCinc.occ = vals["state"]["cinc"]["occ"]
this.statCinc.correlator = vals["state"]["cinc"]["correlator"]
this.statCinc.correlatorFails = vals["state"]["cinc"]["correlatorFails"]
this.statCinc.freqErr = Math.round(vals["state"]["cinc"]["freqErr"] * 100) / 100
this.statCinc.freqErrAcc = Math.round(vals["state"]["cinc"]["freqErrAcc"] * 100) / 100
this.statCinc.channelDelay = vals["state"]["cinc"]["channelDelay"]
{% else %}
this.statTx.state = vals["state"]["tx"]["state"]
this.statTx.modcod = modcodToStr(vals["state"]["tx"]["modcod"])
this.statTx.speedOnTxKbit = Math.round(vals["state"]["tx"]["speedOnTxKbit"] * 100) / 100
this.statTx.speedOnIifKbit = Math.round(vals["state"]["tx"]["speedOnIifKbit"] * 100) / 100
this.statTx.centerFreq = vals["state"]["tx"]["centerFreq"]
this.statTx.symSpeed = vals["state"]["tx"]["symSpeed"]
{% endif %}
this.statDevice.adrv = vals["state"]["device"]["adrv"]
this.statDevice.zynq = vals["state"]["device"]["zynq"]
this.statDevice.fpga = vals["state"]["device"]["fpga"]
{% if modem == 'tdma' %}
this.statDevice.upgradeStatus = vals["state"]["device"]["upgradeStatus"]
this.statDevice.upgradePercent = vals["state"]["device"]["upgradePercent"]
this.statDevice.upgradeImage = vals["state"]["device"]["upgradeImage"]
{% endif %}
// аптайм приходит в секундах, надо преобразовать его в человеко-читаемый вид
let uptime = vals["state"]["device"]["uptime"]
if (uptime) {
let secs = uptime % 60; uptime = Math.floor(uptime / 60)
let mins = uptime % 60; uptime = Math.floor(uptime / 60)
let hours = uptime % 24
uptime = Math.floor( uptime / 24)
let res = `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
if (uptime > 0) { res = `${uptime} дней, ` + res }
this.statOs.uptime = res
} else {
this.statOs.uptime = '?'
}
this.statOs.load1 = Math.round(vals["state"]["device"]["load1min"] * 100) / 100
this.statOs.load5 = Math.round(vals["state"]["device"]["load5min"] * 100) / 100
this.statOs.load15 = Math.round(vals["state"]["device"]["load15min"] * 100) / 100
this.statOs.totalram = vals["state"]["device"]["totalram"]
this.statOs.freeram = vals["state"]["device"]["freeram"]
},
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
this.statRx.packetsDummy = 0
})
},

View File

@@ -0,0 +1,103 @@
{% raw %}
<div class="tabs-body-item tabs-item-flex-container" v-if="activeTab === 'monitoring'">
<div class="settings-set-container statistics-container">
<h2>Статистика приема</h2>
<table>
<tbody>{% endraw %}{% if modem != 'shps' %}{% raw %}
<tr><th>Прием</th><td><span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></td></tr>
<tr><th>Захват символьной</th><td><span :class="{ indicator_bad: statRx.sym_sync_lock === false, indicator_good: statRx.sym_sync_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват ФАПЧ</th><td><span :class="{ indicator_bad: statRx.afc_lock === false, indicator_good: statRx.afc_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват поиска по частоте</th><td><span :class="{ indicator_bad: statRx.freq_search_lock === false, indicator_good: statRx.freq_search_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват пакетной синхр.</th><td><span :class="{ indicator_bad: statRx.pkt_sync === false, indicator_good: statRx.pkt_sync === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ statRx.snr }} / {{ statRx.rssi }}</td></tr>
<tr><th>Modcod</th><td>{{ statRx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ statRx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ statRx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Символьная ошибка</th><td>{{ statRx.symError }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ statRx.freqErr }} / {{ statRx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ statRx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ statRx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ statRx.speedOnRxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statRx.speedOnIifKbit }} кбит/с</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статистика пакетов</td></tr>
<tr><th>Качественных пакетов</th><td>{{ statRx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ statRx.packetsBad }}</td></tr>
<tr><th>DUMMY</th><td>{{ statRx.packetsDummy }}</td></tr>{% endraw %}{% else %}{% raw %}
<tr><th>Прием</th><td><span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ statRx.snr }} / {{ statRx.rssi }}</td></tr>
<tr><th>Частотная ошибка, Гц</th><td>{{ statRx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ statRx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ statRx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ statRx.speedOnRxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statRx.speedOnIifKbit }} кбит/с</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статистика пакетов</td></tr>
<tr><th>Качественных пакетов</th><td>{{ statRx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ statRx.packetsBad }}</td></tr>{% endraw %}{% endif %}{% raw %}
</tbody>
</table>
<button class="action-button" @click="resetPacketsStatistics()"> Сброс статистики </button>
</div>
<div class="settings-set-container statistics-container">
<h2>Статистика передачи</h2>{% endraw %}{% if modem == 'scpc' %}{% raw %}
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ дальнего приема</th><td>{{ statTx.snr }}</td></tr>
<tr><th>Modcod</th><td>{{ statTx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ statTx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ statTx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
</tbody>
</table>{% endraw %}{% elif modem == 'tdma' %}{% raw %}
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>Modcod</th><td>{{ statTx.modcod }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
<tr><th>Центральная частота</th><td>{{ statTx.centerFreq }} кГц</td></tr>
<tr><th>Символьная скорость</th><td>{{ statTx.symSpeed }} ksymb</td></tr>
</tbody>
</table>{% endraw %}{% elif modem == 'shps' %}{% raw %}
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
<tr><th>Центральная частота</th><td>{{ statTx.centerFreq }} кГц</td></tr>
<tr><th>Символьная скорость</th><td>{{ statTx.symSpeed }} ksymb</td></tr>
</tbody>
</table>{% endraw %}{% endif %}{% raw %}
</div>{% endraw %}{% if modem == 'scpc' %}{% raw %}
<div class="settings-set-container statistics-container" v-if="paramRxtx.isCinC === true">
<h2>Статистика режима CinC</h2>
<table>
<tbody>
<tr><th>ОСС</th><td>{{ statCinc.occ }}</td></tr>
<tr><th>Захват коррелятора</th><td><span :class="{ indicator_bad: statCinc.correlator === false, indicator_good: statCinc.correlator === true, indicator: true }"></span></td></tr>
<tr><th>Кол-во срывов коррелятора</th><td>{{ statCinc.correlatorFails }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ statCinc.freqErr }} / {{ statCinc.freqErrAcc }}</td></tr>
<tr><th>Задержка в канале, мс</th><td>{{ statCinc.channelDelay }}</td></tr>
</tbody>
</table>
</div>{% endraw %}{% endif %}{% raw %}
<div class="settings-set-container statistics-container">
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ statDevice.adrv }} °C</td></tr>
<tr><th>Температура ZYNQ</th><td>{{ statDevice.zynq }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ statDevice.fpga }} °C</td></tr>
<tr><th>Время работы устройства</th><td>{{ statOs.uptime }}</td></tr>
<tr><th>Средняя загрузка ЦП (1/5/15 мин.)</th><td>{{ statOs.load1 }}% {{ statOs.load5 }}% {{ statOs.load15 }}%</td></tr>
<tr><th>ОЗУ всего/свободно</th><td>{{ statOs.totalram }}МБ/{{ statOs.freeram }}МБ</td></tr>{% endraw %}{% if modem == 'tdma' %}{% raw %}
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статус обновления</td></tr>
<tr><th>Статус</th><td>{{ statDevice.upgradeStatus }}</td></tr>
<tr><th>Прогресс</th><td>{{ statDevice.upgradePercent }}%</td></tr>
<tr><th>Имя образа</th><td><code>{{ statDevice.upgradeImage }}</code></td></tr>{% endraw %}{% endif %}{% raw %}
</tbody>
</table>
</div>
</div>
{% endraw %}

View File

@@ -0,0 +1,8 @@
submitStatusQos: false,
paramQos: {
en: false,
rt1: [],
rt2: [],
rt3: [],
cd: [],
},

View File

@@ -0,0 +1,224 @@
settingsSubmitQoS() {
if (this.submitStatusQos) { return }
this.submitStatusQos = true
function _translateQosClass(trafficClass, qc) {
let res = {
cir: qc['cir'],
description: qc['description'],
filters: []
}
if (trafficClass === 'cd') {
res.pir = qc.pir
}
if (!qc.isEnabled) {
res.disabled = true
}
for (const fi in qc.filters) {
let filter = {}
if (qc['filters'][fi].vlan !== "") { filter['vlan'] = qc['filters'][fi].vlan }
if (qc['filters'][fi].proto.length > 0) {
let tmp = "";
for (let pid = 0; pid < qc['filters'][fi].proto.length; pid++) {
if (pid !== 0) { tmp += ',' }
tmp += qc['filters'][fi].proto[pid]
}
filter['proto'] = tmp
}
if (qc['filters'][fi].sport !== "") { filter['sport'] = qc['filters'][fi].sport }
if (qc['filters'][fi].dport !== "") { filter['dport'] = qc['filters'][fi].dport }
if (qc['filters'][fi].ip_src !== "") { filter['ip_src'] = qc['filters'][fi].ip_src }
if (qc['filters'][fi].ip_dest !== "") { filter['ip_dest'] = qc['filters'][fi].ip_dest }
if (qc['filters'][fi].dscp !== "") { filter['dscp'] = qc['filters'][fi].dscp }
if (Object.keys(filter).length === 0) { continue }
if (!qc.filters[fi].isEnabled) { filter['disabled'] = true }
res.filters.push(filter)
}
if (res.filters.length === 0) {
// автоматическое выключение класса, если правил нет
res.disabled = true
}
return res
}
let query = {
"en": this.paramQos.en,
"profile": {
"rt1": [],
"rt2": [],
"rt3": [],
"cd": []
}
}
for (let i = 0; i < this.paramQos.rt1.length; i++) { query.profile.rt1.push(_translateQosClass('rt', this.paramQos.rt1[i])) }
for (let i = 0; i < this.paramQos.rt2.length; i++) { query.profile.rt2.push(_translateQosClass('rt', this.paramQos.rt2[i])) }
for (let i = 0; i < this.paramQos.rt3.length; i++) { query.profile.rt3.push(_translateQosClass('rt', this.paramQos.rt3[i])) }
for (let i = 0; i < this.paramQos.cd.length; i++) { query.profile.cd.push(_translateQosClass('rt', this.paramQos.cd[i])) }
//console.log(query)
fetch('/api/set/qos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query), credentials: 'same-origin'
}).then(async (resp) => {
this.submitStatusQos = false
if (resp['error']) { throw new Error(resp['error']) }
this.updateQosSettings(await resp.json())
}).catch((reason) => {
this.submitStatusQos = false
alert(`Ошибка при применении настроек: ${reason}`)
})
},
updateQosSettings(vals) {
this.submitStatusQos = false
this.paramQos.en = vals["settings"]["qos"]["en"]
const qosProfile = vals["settings"]["qos"]["profile"]
if (qosProfile !== null && qosProfile !== undefined) {
this.paramQos.rt1 = [] // .splice(0, this.paramQos.rt1.length)
this.paramQos.rt2 = [] // .splice(0, this.paramQos.rt2.length)
this.paramQos.rt3 = [] // .splice(0, this.paramQos.rt3.length)
this.paramQos.cd = [] // .splice(0, this.paramQos.cd.length)
for (let trafficClass in qosProfile) {
if (['rt1', 'rt2', 'rt3', 'cd'].indexOf(trafficClass) < 0) {
continue
}
if (Array.isArray(qosProfile[trafficClass])) {
for (let i = 0; i < qosProfile[trafficClass].length; i++) {
const qc = qosProfile[trafficClass][i]
let result = {
isEnabled: !qc.hasOwnProperty('disabled'),
cir: qc['cir'],
pir: 0,
description: qc['description'],
filters: []
}
if (trafficClass === 'cd') {
if (qc['pir']) {
result.pir = qc['pir']
}
}
for (let fi = 0; fi < qc['filters'].length; fi++) {
result.filters.push({
isEnabled: !qc['filters'][fi].hasOwnProperty('disabled'),
vlan: qc['filters'][fi].hasOwnProperty('vlan') ? qc['filters'][fi]['vlan'] : '',
proto: qc['filters'][fi].hasOwnProperty('proto') ? qc['filters'][fi]['proto'].split(',') : [],
sport: qc['filters'][fi].hasOwnProperty('sport') ? qc['filters'][fi]['sport'] : '',
dport: qc['filters'][fi].hasOwnProperty('dport') ? qc['filters'][fi]['dport'] : '',
ip_src: qc['filters'][fi].hasOwnProperty('ip_src') ? qc['filters'][fi]['ip_src'] : '',
ip_dest: qc['filters'][fi].hasOwnProperty('ip_dest') ? qc['filters'][fi]['ip_dest'] : '',
dscp: qc['filters'][fi].hasOwnProperty('dscp') ? qc['filters'][fi]['dscp'] : ''
})
}
switch (trafficClass) {
case 'rt1': this.paramQos.rt1.push(result); break
case 'rt2': this.paramQos.rt2.push(result); break
case 'rt3': this.paramQos.rt3.push(result); break
case 'cd': this.paramQos.cd.push(result); break
}
}
}
}
}
},
qosAddClass(name) {
let res = {
isEnabled: true,
cir: 0,
pir: 0,
description: "",
filters: []
}
switch (name) {
case 'rt1': this.paramQos.rt1.push(res); break
case 'rt2': this.paramQos.rt2.push(res); break
case 'rt3': this.paramQos.rt3.push(res); break
case 'cd': this.paramQos.cd.push(res); break
}
},
qosClassAddRule(name, index) {
let rule = {
isEnabled: true,
vlan: "",
proto: [],
sport: "",
dport: "",
ip_src: "",
ip_dest: "",
dscp: ""
}
switch (name) {
case 'rt1': this.paramQos.rt1[index].filters.push(rule); break
case 'rt2': this.paramQos.rt2[index].filters.push(rule); break
case 'rt3': this.paramQos.rt3[index].filters.push(rule); break
case 'cd': this.paramQos.cd[index].filters.push(rule); break
}
},
qosDelClass(name, index) {
switch (name) {
case 'rt1': this.paramQos.rt1.splice(index, 1); break
case 'rt2': this.paramQos.rt2.splice(index, 1); break
case 'rt3': this.paramQos.rt3.splice(index, 1); break
case 'cd': this.paramQos.cd.splice(index, 1); break
}
},
qosDelFilter(name, index, filterIndex) {
switch (name) {
case 'rt1': this.paramQos.rt1[index].filters.splice(filterIndex, 1); break
case 'rt2': this.paramQos.rt2[index].filters.splice(filterIndex, 1); break
case 'rt3': this.paramQos.rt3[index].filters.splice(filterIndex, 1); break
case 'cd': this.paramQos.cd[index].filters.splice(filterIndex, 1); break
}
},
qosGenerateRuleDescription(filter) {
// попытка 1: просто отобразить все фильтры
let result = ""
let isFirst = true;
for (const key in filter) {
if (key === "isEnabled" || !filter[key] || (key === "proto" && filter['proto'].length === 0)) {
continue
}
if (isFirst) {
isFirst = false;
} else {
result += '; '
}
result += `${key}: ${filter[key]}`
}
if (result === "") {
return "пустой"
}
const maxResultLen = 60
if (result.length > maxResultLen) {
// попытка 2, отобразить что вообще в этом фильтре использовалось
result = ""
isFirst = true;
for (const key in filter) {
if (key === "isEnabled" || !filter[key] || (key === "proto" && filter['proto'].length === 0)) {
continue
}
if (isFirst) {
isFirst = false;
} else {
result += ', '
}
result += `${key}`
}
}
return result
},

View File

@@ -0,0 +1,107 @@
{% from 'common/widgets.j2' import build_widget %}
{% raw %}
<div class="tabs-body-item" v-if="activeTab === 'qos' && settingFetchComplete">
<h2>Настройки QoS</h2>
<div class="settings-set-container">
<label>
<span>Активировать QoS</span>
<span class="toggle-input"><input type="checkbox" v-model="paramQos.en" /><span class="slider"></span></span>
</label>
</div>
<div v-for="classesGroup in ['rt1', 'rt2', 'rt3', 'cd']">
<h3>Классы {{ classesGroup.toUpperCase() }} <button class="action-button" @click="qosAddClass(classesGroup)"> + </button></h3>
<details v-for="(qosClass, index) in paramQos[classesGroup]" :key="index" class="settings-set-container">
<summary>
<span v-if="classesGroup === 'cd'">#{{ index }} CIR={{ qosClass.cir }}кбит, PIR={{ qosClass.pir }}кбит {{ qosClass.description }}</span>
<span v-if="classesGroup !== 'cd'">#{{ index }} CBR={{ qosClass.cir }}кбит {{ qosClass.description }}</span>
<span class="summary-actions">
<label>
<span class="toggle-input">
<input type="checkbox" v-model="qosClass.isEnabled" />
<span class="slider"></span>
</span>
</label>
</span>
</summary>
<label>
<span v-if="classesGroup === 'cd'">CIR</span> <span v-if="classesGroup !== 'cd'">CBR</span>
<input v-model="qosClass.cir" type="number"/>
</label>
<label v-if="classesGroup === 'cd'">
<span>PIR</span>
<input v-model="qosClass.pir" type="number"/>
</label>
<label>
<span>Описание</span>
<input v-model="qosClass.description"/>
</label>
<h3>Фильтры ({{ qosClass.filters.length }})</h3>
<div>
<button class="action-button" @click="qosClassAddRule(classesGroup, index)">Добавить правило</button>
</div>
<details v-for="(filter, filterIndex) in qosClass.filters" :key="filterIndex" class="settings-set-container">
<summary>
<span>#{{ filterIndex }} {{ qosGenerateRuleDescription(filter) }}</span>
<span class="summary-actions">
<label>
<span class="toggle-input">
<input type="checkbox" v-model="filter.isEnabled" />
<span class="slider"></span>
</span>
</label>
<button class="dangerous-button" @click="qosDelFilter(classesGroup, index, filterIndex)">Del</button>
</span>
</summary>
<label>
<span>VLAN ID</span>
<!-- singleVlanExpr: (([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4}))-->
<!-- expr: ^(((single,)+single)|single)$-->
<input v-model="filter.vlan" type="text" pattern="^((((([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})),)+(([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})))|(([0-9]{1,4}-[0-9]{1,4})|([0-9]{1,4})))$">
</label>
<div>
<span>Протокол L3</span>
<label class="l3-proto-label"><span>AH:</span><input type="checkbox" value="ah" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>COMP:</span><input type="checkbox" value="comp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>DCCP:</span><input type="checkbox" value="dccp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>ESP:</span><input type="checkbox" value="esp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>ICMP:</span><input type="checkbox" value="icmp" v-model="filter.proto"></label>
<!-- <label class="l3-proto-label"><span>ICMPV6:</span><input type="checkbox" value="icmpv6" v-model="filter.proto"></label>-->
<label class="l3-proto-label"><span>SCTP:</span><input type="checkbox" value="sctp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>TCP:</span><input type="checkbox" value="tcp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>UDP:</span><input type="checkbox" value="udp" v-model="filter.proto"></label>
<label class="l3-proto-label"><span>UDPLITE:</span><input type="checkbox" value="udplite" v-model="filter.proto"></label>
</div>
<label>
<span>Порт источника</span>
<input v-model="filter.sport" type="text" pattern="^((((([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})),)+(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))|(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))$">
</label>
<label>
<span>Порт назначения</span>
<input v-model="filter.dport" type="text" pattern="^((((([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})),)+(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))|(([0-9]{1,5}-[0-9]{1,5})|([0-9]{1,5})))$">
</label>
<label>
<span>IP источника</span>
<input v-model="filter.ip_src" type="text">
</label>
<label>
<span>IP назначения</span>
<input v-model="filter.ip_dest" type="text">
</label>
<label>
<span>Метка IP.DSCP</span>
<input v-model="filter.dscp" type="text">
</label>
</details>
<div>
<button class="dangerous-button" @click="qosDelClass(classesGroup, index)">Удалить класс QoS</button>
</div>
</details>
</div>
<button class="action-button" @click="settingsSubmitQoS()">Применить <span class="submit-spinner" v-show="submitStatusQos"></span></button>
{% endraw %}{% if 'tcpaccel' in params %}
{% for w in params['tcpaccel'] %}{{ build_widget('tcpaccel', w) | indent(12, true) }}{% endfor %}
{% endif %}
</div>

View File

@@ -0,0 +1,34 @@
{% if 'rxtx' in params and modem == 'scpc' %}
calcRequiredSnr(frameSizeNormal, modulation, speed) {
const snrValues = [
{fs: true, mod: 'qpsk', speed: '1/4', snr: 2.6}, {fs: true, mod: 'qpsk', speed: '1/3', snr: 2.6}, {fs: true, mod: 'qpsk', speed: '2/5', snr: 2.6}, {fs: true, mod: 'qpsk', speed: '1/2', snr: 2.6}, {fs: true, mod: 'qpsk', speed: '3/5', snr: 3.1}, {fs: true, mod: 'qpsk', speed: '2/3', snr: 3.8}, {fs: true, mod: 'qpsk', speed: '3/4', snr: 4.5}, {fs: true, mod: 'qpsk', speed: '4/5', snr: 5.2}, {fs: true, mod: 'qpsk', speed: '5/6', snr: 5.5}, {fs: true, mod: 'qpsk', speed: '8/9', snr: 6.4}, {fs: true, mod: 'qpsk', speed: '9/10', snr: 6.7},
{fs: true, mod: '8psk', speed: '3/5', snr: 7.4}, {fs: true, mod: '8psk', speed: '2/3', snr: 8.4}, {fs: true, mod: '8psk', speed: '3/4', snr: 8.7}, {fs: true, mod: '8psk', speed: '5/6', snr: 10}, {fs: true, mod: '8psk', speed: '8/9', snr: 10.9}, {fs: true, mod: '8psk', speed: '9/10', snr: 11.1},
{fs: true, mod: '16apsk', speed: '2/3', snr: 11.2}, {fs: true, mod: '16apsk', speed: '3/4', snr: 11.3}, {fs: true, mod: '16apsk', speed: '4/5', snr: 12.4}, {fs: true, mod: '16apsk', speed: '5/6', snr: 12.7}, {fs: true, mod: '16apsk', speed: '8/9', snr: 13.1}, {fs: true, mod: '16apsk', speed: '9/10', snr: 13.9},
{fs: true, mod: '32apsk', speed: '3/4', snr: 14.5}, {fs: true, mod: '32apsk', speed: '4/5', snr: 14.7}, {fs: true, mod: '32apsk', speed: '5/6', snr: 14.9}, {fs: true, mod: '32apsk', speed: '8/9', snr: 16.1}, {fs: true, mod: '32apsk', speed: '9/10', snr: 16.6},
{fs: false, mod: 'qpsk', speed: '1/4', snr: 4.1}, {fs: false, mod: 'qpsk', speed: '1/3', snr: 4.1}, {fs: false, mod: 'qpsk', speed: '2/5', snr: 4.1}, {fs: false, mod: 'qpsk', speed: '1/2', snr: 4.1}, {fs: false, mod: 'qpsk', speed: '3/5', snr: 4.6}, {fs: false, mod: 'qpsk', speed: '2/3', snr: 5.1}, {fs: false, mod: 'qpsk', speed: '3/4', snr: 5.3}, {fs: false, mod: 'qpsk', speed: '4/5', snr: 6.8}, {fs: false, mod: 'qpsk', speed: '5/6', snr: 7.8}, {fs: false, mod: 'qpsk', speed: '8/9', snr: 8.5},
{fs: false, mod: '8psk', speed: '3/5', snr: 9.5}, {fs: false, mod: '8psk', speed: '2/3', snr: 9.9}, {fs: false, mod: '8psk', speed: '3/4', snr: 10.5}, {fs: false, mod: '8psk', speed: '5/6', snr: 11.1}, {fs: false, mod: '8psk', speed: '8/9', snr: 11.3},
{fs: false, mod: '16apsk', speed: '2/3', snr: 11.6}, {fs: false, mod: '16apsk', speed: '3/4', snr: 11.8}, {fs: false, mod: '16apsk', speed: '4/5', snr: 12}, {fs: false, mod: '16apsk', speed: '5/6', snr: 12.3}, {fs: false, mod: '16apsk', speed: '8/9', snr: 13.4},
{fs: false, mod: '32apsk', speed: '3/4', snr: 14.1}, {fs: false, mod: '32apsk', speed: '4/5', snr: 14.7}, {fs: false, mod: '32apsk', speed: '5/6', snr: 15.3}, {fs: false, mod: '32apsk', speed: '8/9', snr: 16.5},
]
for (let i = 0; i < snrValues.length; i++) {
if (snrValues[i].fs === frameSizeNormal && snrValues[i].mod === modulation && snrValues[i].speed === speed) { return snrValues[i].snr }
}
return '?'
},
calcInterfaceSpeedKb(baud, modulation, speed, frameSizeNormal) {
const mBaud = parseInt(baud.replace(/[^0-9]/g, ''))
const mMod = Math.max(2, ['', '', 'qpsk', '8psk', '16apsk', '32apsk'].indexOf(modulation))
const speedVals = {'1/4': 0.25, '1/3': 0.333, '2/5': 0.4, '1/2': 0.5, '3/5': 0.6, '2/3': 0.666, '3/4': 0.75, '4/5': 0.8, '5/6': 0.833, '8/9': 0.888, '9/10': 0.9}
const mSpeed = speed in speedVals ? speedVals[speed] : 1
const result = (mBaud * mMod * mSpeed) / 1024
const calcSnr = this.calcRequiredSnr(frameSizeNormal, modulation, speed)
let snr;
if (isNaN(calcSnr)) { snr = `ОСШ=?` } else { snr=`ОСШ=${calcSnr}` }
if (result > 1024) {
return toLocaleStringWithSpaces(result / 1024) + ' Мбит/с; ' + snr
} else {
return toLocaleStringWithSpaces(result) + ' кбит/с; ' + snr
}
},
{% endif %}

View File

@@ -0,0 +1,8 @@
{% from 'common/widgets.j2' import build_widget %}
<div class="tabs-body-item" v-if="activeTab === 'setup' && settingFetchComplete">
{% for cat in ['rxtx', 'dpdi', 'buclnb'] %}
{% if cat in params %}
{% for w in params[cat] %}{{ build_widget(cat, w) | indent(12, true) }}{% endfor %}
{% endif %}
{% endfor %}
</div>

View File

@@ -0,0 +1,104 @@
{% macro build_widget_checkbox(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<span class="toggle-input"><input type="checkbox" v-model="param{{ param_group | title }}.{{ widget.name }}" /><span class="slider"></span></span>
</label>{% endmacro %}
{# https://ru.stackoverflow.com/questions/1241064 #}
{% macro build_widget_number(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}><span>{{ widget.label }}</span><input type="number" v-model="param{{ param_group | title }}.{{ widget.name }}"{% if widget['min'] %} min="{{ widget['min'] }}"{% endif %}{% if widget['max'] %} max="{{ widget['max'] }}"{% endif %}{% if widget['step'] %} step="{{ widget['step'] }}"{% endif %}/></label>{% endmacro %}
{% macro js_build_number_number_validator(widget) %}{{ '{' }}{% if widget['min'] %}min:{{ widget['min'] }},{% endif %}{% if widget['max'] %}max:{{ widget['max'] }},{% endif %}{% if widget['step'] %}step:{{ widget['step'] }}{% endif %}{{ '}' }}{% endmacro %}
{% macro build_widget_number_int(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<input type="text" v-model.lazy="param{{ param_group | title }}.{{ widget.name }}" @change="e => {{ build_setter(param_group, widget, "inputFormatNumber(e.target.value, " ~ js_build_number_number_validator(widget) ~ ")") }}"/>
</label>{% endmacro %}
{% macro build_widget_select(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<select v-model="param{{ param_group | title }}.{{ widget.name }}">
{% for opt in widget['values'] %} <option :value="{{ opt.value }}">{{ opt.label }}</option>
{% endfor %}
</select>
</label>{% endmacro %}
{% macro build_widget_watch(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}><span>{{ widget.label }}</span><input type="text" readonly v-model="{{ widget.model }}"/></label>{% endmacro %}
{% macro build_widget_watch_expr(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span><span>{{ '{{ ' ~ widget.expr ~ ' }}' }}</span>
</label>{% endmacro %}
{% macro build_widget_modulation_modcod(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<select v-model="param{{ param_group | title }}.{{ widget.name }}Modulation" @change="param{{ param_group | title }}.{{ widget.name }}Speed = correctModcodSpeed(param{{ param_group | title }}.{{ widget.name }}Modulation, param{{ param_group | title }}.{{ widget.name }}Speed)">
<option :value="'qpsk'">QPSK</option>
<option :value="'8psk'">8PSK</option>
<option :value="'16apsk'">16APSK</option>
<option :value="'32apsk'">32APSK</option>
</select>
</label>{% endmacro %}
{% macro build_widget_modulation_speed(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<select v-model="param{{ param_group | title }}.{{ widget.name }}Speed">
<option v-for="speed in getAvailableModcods(param{{ param_group | title }}.{{ widget.name }}Modulation)" v-bind:value="speed">{{ '{{' }} speed {{ '}}' }}</option>
</select>
</label>{% endmacro %}
{% macro build_widget_flex_container(param_group, widget) %}<div class="tabs-item-flex-container"{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
{% for w in widget.childs %}{{ build_widget(param_group, w) | indent(4, true) }}{% endfor %}
</div>{% endmacro %}
{% macro build_widget_settings_container(param_group, widget) %}<div class="settings-set-container"{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
{% for w in widget.childs %}{{ build_widget(param_group, w) | indent(4, true) }}{% endfor %}
</div>{% endmacro %}
{% macro build_widget_ip_address(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<input v-model="param{{ param_group | title }}.{{ widget.name }}" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</label>{% endmacro %}
{% macro build_widget_ip_address_mask(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<input v-model="param{{ param_group | title }}.{{ widget.name }}" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$">
</label>{% endmacro %}
{% macro build_widget_text(param_group, widget) %}<label{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>
<span>{{ widget.label }}</span>
<input v-model="param{{ param_group | title }}.{{ widget.name }}" type="text">
</label>{% endmacro %}
{% macro build_widget(param_group, widget) %}{% if widget.widget == 'flex-container' %}{{ build_widget_flex_container(param_group, widget) }}
{% elif widget.widget == 'settings-container' %}{{ build_widget_settings_container(param_group, widget) }}
{% elif widget.widget == 'h2' %}<h2{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>{{ widget.label }}</h2>
{% elif widget.widget == 'h3' %}<h3{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>{{ widget.label }}</h3>
{% elif widget.widget == 'submit' %}<button class="action-button" @click="settingsSubmit{{ param_group | title }}()"{% if widget.v_show %} v-show="{{ widget.v_show }}"{% endif %}>Сохранить <span class="submit-spinner" v-show="submitStatus.{{ param_group }}"></span></button>
{% elif widget.widget == 'checkbox' %}{{ build_widget_checkbox(param_group, widget) }}
{% elif widget.widget == 'number' %}{{ build_widget_number(param_group, widget) }}
{% elif widget.widget == 'number-int' %}{{ build_widget_number_int(param_group, widget) }}
{% elif widget.widget == 'watch' %}{{ build_widget_watch(param_group, widget) }}
{% elif widget.widget == 'watch-expr' %}{{ build_widget_watch_expr(param_group, widget) }}
{% elif widget.widget == 'select' %}{{ build_widget_select(param_group, widget) }}
{% elif widget.widget == 'modulation-modcod' %}{{ build_widget_modulation_modcod(param_group, widget) }}
{% elif widget.widget == 'modulation-speed' %}{{ build_widget_modulation_speed(param_group, widget) }}
{% elif widget.widget == 'ip-address' %}{{ build_widget_ip_address(param_group, widget) }}
{% elif widget.widget == 'ip-address-mask' %}{{ build_widget_ip_address_mask(param_group, widget) }}
{% elif widget.widget == 'text' %}{{ build_widget_text(param_group, widget) }}
{% else %}<p>Widget '{{ widget.widget }}' not defined!</p><p>{{ widget }}</p>
{% endif %}
{% endmacro %}
{% macro build_getter_js(param_group, widget) %}{% if widget.widget in ['flex-container', 'settings-container', 'h2', 'h3', 'submit', 'watch', 'watch-expr'] %}null{%
elif widget.widget in ['checkbox', 'number', 'select', 'ip-address', 'ip-address-mask', 'modulation-modcod', 'modulation-speed', 'text'] %}this.param{{ param_group | title }}.{{ widget.name }}{%
elif widget.widget == 'number-int' %}parseFloat(this.param{{ param_group | title }}.{{ widget.name }}.replace(/[^0-9,.]/g, '').replace(',', '.')){%
else %}<p>Widget '{{ widget.widget }}' not defined!</p><p>{{ widget }}</p>{% endif %}{% endmacro %}
{% macro build_setter_js(param_group, widget, expr) %}{% if widget.widget in ['flex-container', 'settings-container', 'h2', 'h3', 'submit', 'watch', 'watch-expr'] %}null{%
elif widget.widget in ['checkbox', 'number', 'select', 'ip-address', 'ip-address-mask', 'modulation-modcod', 'modulation-speed', 'text'] %}this.param{{ param_group | title }}.{{ widget.name }} = {{ expr }}{%
elif widget.widget == 'number-int' %}this.param{{ param_group | title }}.{{ widget.name }} = this.inputFormatNumber({{ expr }}, {{ js_build_number_number_validator(widget) }}){%
else %}<p>Widget '{{ widget.widget }}' not defined!</p><p>{{ widget }}</p>{% endif %}{% endmacro %}
{% macro build_setter(param_group, widget, expr) %}{% if widget.widget in ['flex-container', 'settings-container', 'h2', 'h3', 'submit', 'watch', 'watch-expr'] %}null{%
elif widget.widget in ['checkbox', 'number', 'select', 'ip-address', 'ip-address-mask', 'modulation-modcod', 'modulation-speed', 'text'] %}param{{ param_group | title }}.{{ widget.name }} = {{ expr }}{%
elif widget.widget == 'number-int' %}param{{ param_group | title }}.{{ widget.name }} = inputFormatNumber({{ expr }}, {{ js_build_number_number_validator(widget) }}){%
else %}<p>Widget '{{ widget.widget }}' not defined!</p><p>{{ widget }}</p>{% endif %}{% endmacro %}

View File

@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ modem_name }}</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/fields.css">
<style>{% raw %}
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: var(--bg-selected);
}
body { /* значение по-умолчанию */ --header-height: 60px; }
#content {
padding-top: var(--header-height);
}
.l3-proto-label {
margin: 0 0 0 0.5em;
}
.l3-proto-label > * {
display: inline-block;
}
.l3-proto-label input[type=checkbox] {
width: auto;
}
{% endraw %}
</style>
</head>
<body>
<div id="app" hidden>
<header>{% raw %}
<span class="nav-bar-element">Прием: <span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Передача: <span :class="{ indicator_good: statTx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Тест: <span :class="{ indicator_good: testState, indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
<span :class="{ value_bad: initState !== 'Успешная инициализация системы' }">{{ initState }}</span>
{% endraw %}
<div class="tabs-header">
<span style="font-weight:bold">{{ modem_name }}</span>
{% for tab in header_tabs %}
<a href="#{{ tab.name }}" class="tabs-btn" @click="activeTab = '{{ tab.name }}'" :class="{{ '{' }} active: activeTab === '{{ tab.name }}' {{ '}' }}">{{ tab.desc }}</a>
{% endfor %}
<a href="/logout" class="tabs-btn">Выход</a>
</div>
</header>
<div id="content">
{% for tab in header_tabs %}{% include 'common/' ~ tab.name ~ '.html.j2' %}{% endfor %}
{% raw %}
<p>Последнее обновление статистики: {{ lastUpdateTime }}</p>
</div>
{% endraw %}
</div>
<script src="/js/vue.js?v=3.5.13"></script>
<script>
const availableTabs = ['{{ tab_names_array | join("', '") }}']
// для обновления высоты хидера
function updateHeaderHeight() { const header = document.querySelector('header'); document.body.style.setProperty('--header-height', `${header.offsetHeight}px`); }
window.addEventListener('load', updateHeaderHeight); window.addEventListener('resize', updateHeaderHeight);
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return availableTabs[0]
}
function toLocaleStringWithSpaces(num) {
if (typeof num !== 'number') {
if (typeof num === 'string') { return num }
return String(num);
}
const numberString = num.toString()
const [integerPart, fractionalPart] = numberString.split('.')
const spacedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
if (fractionalPart) { return `${spacedIntegerPart}.${fractionalPart}` }
else { return spacedIntegerPart }
}
const app = Vue.createApp({
data() {
return {
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
{% for pg in paramGroupsList %}
{{ pg }}: false,
{% endfor %}
firmwareUpload: false,
firmwareUpgrade: false,
{% if modem == 'tdma' %}
firmwareUpgradeOta: false,
cesPassword: false,
{% endif %}
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
{% if modem == 'tdma' %}
cesPasswordValue: '',
{% endif %}
// ========== include from 'common/all-params-data.js.j2'
{% include 'common/all-params-data.js.j2' %}
// ========== include end from 'common/all-params-data.js.j2'
{% for tab in header_tabs %}
// ========== include from '{{ 'common/' ~ tab.name ~ '-data.js.j2' }}'
{% include 'common/' ~ tab.name ~ '-data.js.j2' ignore missing %}
// ========== include end from '{{ 'common/' ~ tab.name ~ '-data.js.j2' }}'
{% endfor %}
uploadFw: {
progress: null,
filename: null,
sha256: null
},
// эти "настройки" - read only
about: {
firmwareVersion: '?',
modemUid: '?',
modemSn: '?',
macManagement: '?',
macData: '?',
},
testState: false,
initState: '',
lastUpdateTime: new Date(),
activeTab: getCurrentTab(),
settingFetchComplete: false,
}
},
methods: {
correctModcodSpeed(modulation, speed) {
const mod = modulation.toLowerCase()
const available = {
"qpsk": ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"8psk": ['2/3', '3/4', '5/6', '8/9', '9/10'],
"16apsk": ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"32apsk": ['3/4', '4/5', '5/6', '8/9', '9/10']
}
if (mod in available) {
if (available[mod].indexOf(speed) >= 0) {
return speed
}
return available[mod][0]
}
return ""
},
getAvailableModcods(modulation) {
// NOTE модкоды со скоростью хода 3/5 не работают
switch (modulation) {
case 'qpsk':
return ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '8psk':
return ['3/5', '2/3', '3/4', '5/6', '8/9', '9/10']
case '16apsk':
return ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '32apsk':
return ['3/4', '4/5', '5/6', '8/9', '9/10']
default:
return []
}
},
inputFormatNumber(src, validation) {
if (validation === null || validation === undefined) { validation = {} }
const rawVal = src.toString().replace(/[^0-9.,]/g, '').replace(',', '.')
let result = rawVal === '' ? 0 : parseFloat(rawVal)
const step = 'step' in validation ? validation['step']: 1.0
result = Math.round(result / step) * step
if ('min' in validation) { if (result <= validation['min']) { result = validation['min'] } }
if ('max' in validation) { if (result >= validation['max']) { result = validation['max'] } }
return toLocaleStringWithSpaces(result)
},
// ========== include from 'common/all-params-methods.js.j2'
{% include 'common/all-params-methods.js.j2' %}
// ========== include end from 'common/all-params-methods.js.j2'
{% for tab in header_tabs %}
// ========== include from '{{ 'common/' ~ tab.name ~ '-methods.js.j2' }}'
{% include 'common/' ~ tab.name ~ '-methods.js.j2' ignore missing %}
// ========== include end from '{{ 'common/' ~ tab.name ~ '-methods.js.j2' }}'
{% endfor %}
performUpdateSettings() {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
let vals = await d.json()
this.settingFetchComplete = true
{% for pg in paramGroupsList %}
this.update{{ pg | capitalize }}Settings(vals)
{% endfor %}
{% if 'qos' in tab_names_array %}
this.updateQosSettings(vals)
{% endif %}
if ('netServerName' in vals['settings']) {
document.getElementsByTagName('title')[0].innerText = vals['settings']['netServerName']
}
}
doFetchSettings().then(() => {})
}
},
mounted() {
const doFetchStatistics = async () => {
if (this.submitStatus.modemReboot !== null) {
this.initState = `Перезагрузка модема... Осталось ${this.submitStatus.modemReboot} сек`
this.submitStatus.modemReboot--
if (this.submitStatus.modemReboot <= 0) {
window.location.reload()
}
} else {
try {
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"
}
}
setTimeout(() => {
doFetchStatistics()
}, 1000)
}
const doFetchAbout = async () => {
try {
const fr = await fetch("/api/get/aboutFirmware")
const d = await fr.json()
this.about.firmwareVersion = d["firmware"]["version"]
this.about.modemUid = d["firmware"]["modemId"]
this.about.modemSn = d["firmware"]["modemSn"]
this.about.macManagement = d["firmware"]["macMang"]
this.about.macData = d["firmware"]["macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
});
app.mount('#app')
</script>
</body>
</html>

283
src/api-driver/daemon.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include "daemon.h"
#include <utility>
#include "terminal_api_driver.h"
// минимальный порог для сна в цикле событий демона
static constexpr int64_t SLEEP_THRESHOLD = 10;
int64_t api_driver::TimeNow() {
return std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
}
namespace api_driver {
/**
* Обертка для объектов, доступных для обновления
* NOTE: перед вызовом функций, требующих `TSID`, необходимо захватить мютекс API.
*/
class CpUpdatebleObject {
public:
int64_t lastUpdate = 0;
int64_t updatePeriodMs = -1;
/**
* Функция для обновления (загрузки) объекта из CP API.
*/
std::function<void ()> updateCallback;
explicit CpUpdatebleObject(std::function<void ()> callback, int64_t period = -1): updatePeriodMs(period), updateCallback(std::move(callback)) {}
bool checkNeedUpdate(int64_t now) const {
if (updatePeriodMs < 0) return false;
// тут нет смысла спать меньше чем на 20мс, поэтому можно разрешить чтение на некоторое время раньше
return now - lastUpdate >= (updatePeriodMs - 20);
}
int64_t getNextUpdate(int64_t now) const {
if (checkNeedUpdate(now)) {
return 0;
}
auto next = now - lastUpdate;
return next < 0 ? 0 : next;
}
~CpUpdatebleObject() = default;
};
}
void api_driver::TerminalApiDaemon::connectToApi() {
{
std::lock_guard _lock(this->stateMutex);
this->state.fInitState = "Not connected to API";
}
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
{
std::lock_guard _lock(this->settingsMutex);
this->settingsNetwork.loadDefaults();
}
#endif
for (int connectAttempt = 0;; connectAttempt++) {
BOOST_LOG_TRIVIAL(info) << "api_driver::TerminalApiDaemon::connectToApi(): Try to connect api (attempt " << connectAttempt << ")...";
try {
cp.connect();
std::string tmp = cp.getDmaDebug("status_init");
{
std::lock_guard _lock(this->stateMutex);
this->state.fInitState = tmp;
}
BOOST_LOG_TRIVIAL(info) << "api_driver::TerminalApiDaemon::connectToApi(): Success connect!";
BOOST_LOG_TRIVIAL(info) << "api_driver::TerminalApiDaemon::connectToApi(): API status: " << tmp;
obj::TerminalFirmwareVersion f;
f.load(cp);
{
std::lock_guard _lock(this->firmwareMutex);
this->firmware = f;
}
cp.lastCpError = OK;
break;
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "api_driver::TerminalApiDaemon::connectToApi(): connect error " << e.what();
}
boost::this_thread::sleep_for(boost::chrono::duration(boost::chrono::milliseconds(1000)));
}
}
void api_driver::TerminalApiDaemon::run() {
// это демон, который в бесконечном цикле опрашивает API
this->connectToApi();
struct {
CpUpdatebleObject uo;
std::string updaterName;
} updaters[] = {
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
// обновление логов
{.uo = CpUpdatebleObject([this]() {
this->statsLogs.updateCallback(cp);
}), .updaterName = "updateDebugMetrics"},
#endif
// обновление статистики
{.uo = CpUpdatebleObject([this]() {
obj::TerminalState tmp;
{
std::shared_lock _slock1(this->settingsMutex);
tmp = state;
}
tmp.updateCallback(cp);
std::lock_guard _slock2(this->settingsMutex);
state = tmp;
}, CACHE_STATISTICS_UPDATE_MS), .updaterName = "updateStatistics"},
{.uo = CpUpdatebleObject([this]() {
obj::TerminalDeviceState tmp(stateDev);
tmp.updateCallback(cp);
std::lock_guard _slock2(this->settingsMutex);
stateDev = tmp;
}, CACHE_STATISTICS_UPDATE_MS), .updaterName = "updateDeviceState"},
// обновление кеша настроек
{.uo = CpUpdatebleObject([this]() {
obj::TerminalRxTxSettings rxtx;
rxtx.updateCallback(cp);
std::lock_guard _slock2(this->settingsMutex);
settingsRxTx = rxtx;
}, CACHE_SETTINGS_UPDATE_MS), .updaterName = "updateRxTxSettings"},
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
{.uo = CpUpdatebleObject([this]() {
obj::TerminalNetworkSettings net;
{
std::shared_lock _slock1(this->settingsMutex);
net = settingsNetwork;
}
net.updateCallback(cp);
std::lock_guard _slock2(this->settingsMutex);
settingsNetwork = net;
}, CACHE_SETTINGS_UPDATE_MS), .updaterName = "updateNetworkSettings"},
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
// обновление кеша QoS
{.uo = CpUpdatebleObject([this]() {
obj::TerminalQosSettings qos;
qos.updateCallback(cp);
std::lock_guard _slock(this->settingsMutex);
settingsQos = qos;
}, CACHE_SETTINGS_UPDATE_MS), .updaterName = "updateQosSettings"},
#endif
};
while (true) {
if (this->cp.lastCpError == ERROR || this->cp.lastCpError == TIMEOUT) {
BOOST_LOG_TRIVIAL(error) << "api_driver::TerminalApiDaemon::run(): close current daemon session caused error " << this->cp.lastCpError;
cp.disconnect();
this->connectToApi();
}
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
updaters[0].uo.updatePeriodMs = this->statsLogs.logEn ? this->statsLogs.logPeriodMs.load() : -1;
#endif
int64_t sleepTime = 60000; // минута по-умолчанию
auto now = TimeNow();
for (auto& u: updaters) {
if (u.uo.checkNeedUpdate(now)) {
auto targetTime = u.uo.lastUpdate + u.uo.updatePeriodMs;
if (targetTime + SLEEP_THRESHOLD <= now && targetTime - SLEEP_THRESHOLD >= now) {
u.uo.lastUpdate = targetTime;
} else {
u.uo.lastUpdate = now;
}
try {
std::lock_guard _lock(this->cpApiMutex);
u.uo.updateCallback();
BOOST_LOG_TRIVIAL(debug) << "api_driver::TerminalApiDaemon::run()->" << u.updaterName << "(): success update!";
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "api_driver::TerminalApiDaemon::run()->" << u.updaterName << "(): error " << e.what();
}
now = TimeNow();
}
if (u.uo.updatePeriodMs >= 0) {
sleepTime = std::min(sleepTime, u.uo.getNextUpdate(now));
}
}
if (sleepTime > 0) {
boost::this_thread::sleep_for(boost::chrono::duration(boost::chrono::milliseconds(sleepTime)));
}
}
}
api_driver::TerminalApiDaemon::TerminalApiDaemon(): daemon([this]() { this->run(); }) {}
void api_driver::TerminalApiDaemon::getState(obj::TerminalState &dest) {
std::shared_lock _lock(stateMutex);
dest = state;
}
void api_driver::TerminalApiDaemon::getDeviceState(obj::TerminalDeviceState &dest) {
std::shared_lock _lock(stateMutex);
dest = stateDev;
}
api_driver::obj::TerminalRxTxSettings api_driver::TerminalApiDaemon::getSettingsRxTx() {
obj::TerminalRxTxSettings s;
{
std::shared_lock _olock(this->settingsMutex);
s = settingsRxTx;
}
return s;
}
void api_driver::TerminalApiDaemon::setSettingsRxTx(obj::TerminalRxTxSettings &s) {
std::lock_guard _olock(settingsMutex);
settingsRxTx = s;
}
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
api_driver::obj::TerminalNetworkSettings api_driver::TerminalApiDaemon::getNetworkSettings() {
obj::TerminalNetworkSettings s;
{
std::shared_lock _olock(this->settingsMutex);
s = settingsNetwork;
}
return s;
}
void api_driver::TerminalApiDaemon::setNetworkSettings(obj::TerminalNetworkSettings &s) {
std::lock_guard _olock(settingsMutex);
settingsNetwork = s;
}
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
api_driver::obj::TerminalQosSettings api_driver::TerminalApiDaemon::getQosSettings() {
obj::TerminalQosSettings s;
{
std::shared_lock _olock(this->settingsMutex);
s = settingsQos;
}
return s;
}
void api_driver::TerminalApiDaemon::setQosSettings(obj::TerminalQosSettings &s) {
std::lock_guard _olock(settingsMutex);
settingsQos = s;
}
#endif
api_driver::obj::TerminalFirmwareVersion api_driver::TerminalApiDaemon::getFirmware() {
obj::TerminalFirmwareVersion res;
{
std::shared_lock _olock(firmwareMutex);
res = firmware;
}
return res;
}
void api_driver::TerminalApiDaemon::resetPacketStatistics() {
std::lock_guard lock(this->cpApiMutex);
cp.getDmaDebug("reset_cnt_rx");
}
void api_driver::TerminalApiDaemon::resetDefaultSettings() {
std::lock_guard lock(this->cpApiMutex);
cp.setDmaDebug("begin_save_config", "");
cp.setDmaDebug("default_params", "");
cp.setDmaDebug("save_config", "");
}
api_driver::TerminalApiDaemon::~TerminalApiDaemon() {
try {
daemon.interrupt();
daemon.try_join_for(boost::chrono::seconds(2));
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "api_driver::~TerminalApiDaemon(): " << e.what();
}
}

79
src/api-driver/daemon.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef API_DRIVER_DAEMON_H
#define API_DRIVER_DAEMON_H
#include "proxy.h"
#include "api-driver/structs.h"
#include <boost/thread/thread.hpp>
namespace api_driver {
int64_t TimeNow();
class TerminalApiDaemon {
boost::thread daemon;
void connectToApi();
void run();
std::shared_mutex stateMutex;
obj::TerminalState state;
obj::TerminalDeviceState stateDev;
std::shared_mutex settingsMutex;
obj::TerminalRxTxSettings settingsRxTx;
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
obj::TerminalNetworkSettings settingsNetwork;
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
obj::TerminalQosSettings settingsQos;
#endif
std::shared_mutex firmwareMutex;
obj::TerminalFirmwareVersion firmware;
public:
std::mutex cpApiMutex;
proxy::CpProxy cp;
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
obj::StatisticsLogger statsLogs;
#endif
explicit TerminalApiDaemon();
/**
* Получение статистики, копирует текущие значения в структуры, переданные по указателю. Если передан пустой указатель, копирования не произойдет.
*/
void getState(obj::TerminalState &dest);
void getDeviceState(obj::TerminalDeviceState &dest);
// /**
// * Получение настроек, копирует текущие значения в структуры, переданные по указателю. Если передан пустой указатель, копирования не произойдет.
// * Установка настроек просто копирует настройки и устанавливает их текущими
// */
obj::TerminalRxTxSettings getSettingsRxTx();
void setSettingsRxTx(obj::TerminalRxTxSettings &s);
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
obj::TerminalNetworkSettings getNetworkSettings();
void setNetworkSettings(obj::TerminalNetworkSettings &s);
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
obj::TerminalQosSettings getQosSettings();
void setQosSettings(obj::TerminalQosSettings &s);
#endif
obj::TerminalFirmwareVersion getFirmware();
void resetPacketStatistics();
void resetDefaultSettings();
~TerminalApiDaemon();
};
}
#endif //API_DRIVER_DAEMON_H

137
src/api-driver/proxy.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include "proxy.h"
#include "sstream"
#define CPAPI_PROXY_CALL_HELPER(callfrom, func, funcArgs, throwArgs) do { lastCpError = func funcArgs; if (lastCpError != OK) { \
std::stringstream err; err << callfrom ": CP Api error " #func "("; err << throwArgs; err << "): " << lastCpError;\
throw std::runtime_error(err.str());\
} } while (0)
api_driver::proxy::CpProxy::CpProxy() = default;
api_driver::proxy::CpProxy::CpProxy(TSID s): sid(s) {}
void api_driver::proxy::CpProxy::connect() {
unsigned int access{};
CPAPI_PROXY_CALL_HELPER("CpProxy::connect", CP_Login, ("admin", "pass", &sid, &access), R"("admin", "pass", &sid, &access)");
}
void api_driver::proxy::CpProxy::disconnect() {
if (sid != 0) {
lastCpError = CP_Logout(sid);
sid = 0;
}
}
std::string api_driver::proxy::CpProxy::getDmaDebug(const std::string &arg) {
std::string result;
CPAPI_PROXY_CALL_HELPER("CpProxy::getDmaDebug", CP_GetDmaDebug, (sid, arg.c_str(), &result), arg);
return result;
}
void api_driver::proxy::CpProxy::setDmaDebug(const std::string &arg, const std::string &value) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setDmaDebug", CP_SetDmaDebug, (sid, arg.c_str(), value), arg << ", \"" << value << "\"");
}
std::string api_driver::proxy::CpProxy::getNetwork(const std::string &param) {
std::string result;
CPAPI_PROXY_CALL_HELPER("CpProxy::getNetwork", CP_GetNetwork, (sid, param.c_str(), &result), param);
return result;
}
void api_driver::proxy::CpProxy::setNetwork(const std::string &param, const std::string &value) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setNetwork", CP_SetNetwork, (sid, param.c_str(), value.c_str()), param << ", \"" << value << "\"");
}
void api_driver::proxy::CpProxy::getModState(modulator_state &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getModState", CP_GetModulatorState, (sid, dest), "");
}
void api_driver::proxy::CpProxy::getModSettings(modulator_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getModSettings", CP_GetModulatorSettings, (sid, dest), "");
}
void api_driver::proxy::CpProxy::setModSettings(modulator_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setModSettings", CP_SetModulatorSettings, (sid, dest), "struct {...}");
}
void api_driver::proxy::CpProxy::getDemodState(demodulator_state &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getDemodState", CP_GetDemodulatorState, (sid, dest), "");
}
void api_driver::proxy::CpProxy::getDemodSettings(demodulator_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getDemodSettings", CP_GetDemodulatorSettings, (sid, dest), "");
}
void api_driver::proxy::CpProxy::setDemodSettings(demodulator_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setDemodSettings", CP_SetDemodulatorSettings, (sid, dest), "struct {...}");
}
#ifdef API_STRUCT_ACM_ENABLE
void api_driver::proxy::CpProxy::getAcmSettings(ACM_parameters_serv_ &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getAcmSettings", CP_GetAcmParams, (sid, &dest), "");
}
void api_driver::proxy::CpProxy::setAcmSettings(ACM_parameters_serv_ &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setAcmSettings", CP_SetAcmParams, (sid, dest), "struct {...}");
}
#endif
void api_driver::proxy::CpProxy::getDeviceState(device_state &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getDeviceState", CP_GetDeviceState, (sid, dest), "");
}
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
std::tuple<std::string, bool> api_driver::proxy::CpProxy::getQosSettings() {
std::string rules;
bool en;
CPAPI_PROXY_CALL_HELPER("CpProxy::getQosSettings", CP_GetQoSSettings, (sid, rules, en), "");
return {rules, en};
}
void api_driver::proxy::CpProxy::setQosSettings(const std::string &rules, bool enable) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setQosSettings", CP_SetQoSSettings, (sid, rules, enable), "`" << rules << "`, " << (enable ? "true" : "false"));
}
#endif
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
void api_driver::proxy::CpProxy::getDpdiSettings(DPDI_parmeters &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getDpdiSettings", CP_GetDpdiParams, (sid, &dest), "");
}
void api_driver::proxy::CpProxy::setDpdiSettings(DPDI_parmeters &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setDpdiSettings", CP_SetDpdiParams, (sid, dest), "struct {...}");
}
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
void api_driver::proxy::CpProxy::getBuclnbSettings(buc_lnb_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getBuclnbSettings", CP_GetBUC_LNB_settings, (sid, dest), "");
}
void api_driver::proxy::CpProxy::setBuclnbSettings(buc_lnb_settings &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::setBuclnbSettings", CP_SetBUC_LNB_settings, (sid, dest), "struct {...}");
}
#endif
#ifdef MODEM_IS_SCPC
void api_driver::proxy::CpProxy::getCincState(CinC_state &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getCincState", CP_GetCinCState, (sid, dest), "");
}
#endif
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
void api_driver::proxy::CpProxy::getDebugMetrics(debug_metrics &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getDebugMetrics", CP_GetDebugMetrics, (sid, dest), "");
}
#endif
#ifdef MODEM_IS_TDMA
void api_driver::proxy::CpProxy::getUpdateStatus(progress_msg &dest) {
CPAPI_PROXY_CALL_HELPER("CpProxy::getUpdateStatus", CP_GetUpdateStatus, (sid, dest), "");
}
#endif
api_driver::proxy::CpProxy::~CpProxy() {
disconnect();
}

82
src/api-driver/proxy.h Normal file
View File

@@ -0,0 +1,82 @@
#ifndef PROXY_H
#define PROXY_H
#include "api-driver/stricts-enable.h"
#include <boost/log/trivial.hpp>
#include <terminal_api/ControlProtoCInterface.h>
std::ostream& operator<<(std::ostream& out, CP_Result result);
namespace api_driver::proxy {
class CpProxy {
public:
TSID sid;
CP_Result lastCpError = OK;
CpProxy();
CpProxy(TSID s);
/**
* Вызывает процедуру подключения к CP_API. Бросает исключение, если не удалось подключиться.
*/
void connect();
/**
* Процедура отключения от API, после ее вызова запрещается использовать CP_API
*/
void disconnect();
std::string getDmaDebug(const std::string& arg);
void setDmaDebug(const std::string& arg, const std::string& value);
std::string getNetwork(const std::string& param);
void setNetwork(const std::string& param, const std::string& value);
void getModState(modulator_state& dest);
void getModSettings(modulator_settings& dest);
void setModSettings(modulator_settings& dest);
void getDemodState(demodulator_state& dest);
void getDemodSettings(demodulator_settings& dest);
void setDemodSettings(demodulator_settings& dest);
#ifdef API_STRUCT_ACM_ENABLE
void getAcmSettings(ACM_parameters_serv_& dest);
void setAcmSettings(ACM_parameters_serv_& dest);
#endif
void getDeviceState(device_state& dest);
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
std::tuple<std::string, bool> getQosSettings();
void setQosSettings(const std::string& rules, bool enable);
#endif
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
void getDpdiSettings(DPDI_parmeters& dest);
void setDpdiSettings(DPDI_parmeters& dest);
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
void getBuclnbSettings(buc_lnb_settings& dest);
void setBuclnbSettings(buc_lnb_settings& dest);
#endif
#ifdef MODEM_IS_SCPC
void getCincState(CinC_state& dest);
#endif
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
void getDebugMetrics(debug_metrics& dest);
#endif
#ifdef MODEM_IS_TDMA
void getUpdateStatus(progress_msg& dest);
#endif
~CpProxy();
};
}
#endif //PROXY_H

View File

@@ -0,0 +1,30 @@
#ifndef API_DRIVER_STRICTS_ENABLE_H
#define API_DRIVER_STRICTS_ENABLE_H
#if defined(MODEM_IS_SCPC)
#define API_OBJECT_DEBUG_METRICS_ENABLE
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_SHPS)
#define API_STRUCT_ACM_ENABLE
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_TDMA) || defined(MODEM_IS_SHPS)
#define API_OBJECT_NETWORK_SETTINGS_ENABLE
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_TDMA)
#define API_OBJECT_QOS_SETTINGS_ENABLE
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_TDMA)
#define API_OBJECT_DPDI_SETTINGS_ENABLE
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_TDMA) || defined(MODEM_IS_SHPS)
#define API_OBJECT_BUCLNB_SETTINGS_ENABLE
#endif
#endif //API_DRIVER_STRICTS_ENABLE_H

975
src/api-driver/structs.cpp Normal file
View File

@@ -0,0 +1,975 @@
#include "api-driver/structs.h"
#include "api-driver/proxy.h"
#include "common/nlohmann/json.hpp"
#include <iomanip>
#include <sys/sysinfo.h>
#include <boost/property_tree/ptree.hpp>
#include "terminal_api_driver.h"
#define TIME_NOW() std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count()
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
static inline const char* boolAsStr(bool value) {
return value ? "true" : "false";
}
std::ostream& operator<<(std::ostream& out, CP_Result result) {
switch (result) {
case OK: out << "OK"; break;
case TIMEOUT: out << "TIMEOUT"; break;
case ERROR: out << "ERROR"; break;
case ABORT: out << "ABORT"; break;
case BUSY: out << "BUSY"; break;
default:
out << static_cast<int>(result);
}
return out;
}
std::string makeTimepointFromMillis(int64_t unix_time_ms) {
// Преобразуем миллисекунды в микросекунды для std::chrono
auto time_point = std::chrono::time_point<std::chrono::system_clock,
std::chrono::microseconds>(std::chrono::microseconds(unix_time_ms * 1000));
auto tp = std::chrono::system_clock::to_time_t(time_point);
tm* t = std::localtime(&tp);
std::stringstream ss;
ss << std::put_time(t, "%Y-%m-%d %H:%M:%S");
auto ms = (unix_time_ms % 1000);
ss << '.' << std::setw(3) << std::setfill('0') << ms;
return ss.str();
}
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
api_driver::obj::StatisticsLogger::StatisticsLogger(): timeStart(TIME_NOW()) {}
nlohmann::json api_driver::obj::StatisticsLogger::getSettings() {
std::lock_guard _lock(mutex);
nlohmann::json res;
res["en"] = this->logEn;
res["logPeriodMs"] = logPeriodMs.load();
res["maxAgeMs"] = maxAgeMs.load();
return res;
}
void api_driver::obj::StatisticsLogger::setSettings(const nlohmann::json& data) {
const bool newEn = data.value("en", logEn);
const int newInterval = data.value("logPeriodMs", logPeriodMs.load());
const int newMaxAgeMs = data.value("maxAgeMs", maxAgeMs.load());
std::lock_guard _lock(this->mutex);
this->logPeriodMs = newInterval;
this->maxAgeMs = newMaxAgeMs;
if (newEn != this->logEn) {
if (newEn) {
this->logFile.open(LOG_FILENAME, std::ios::out);
if (this->logFile.is_open()) {
const auto* header = "timestamp\tcnt ok\tcnt bad\tfine freq dem\tcrs freq dem\tcrs freq compensator\tcrs time est\tfine time est\tmax level corr\tcurrent delay\tSNR\tcurrent modcod\tfine freq compensator\tind freq grb\tind freq tochn\tind filt adapt\tfilter corr cinc\tcorr cnt\tRSS\tcor erl\tcor lat\tgc gain\tpower pl rx\n";
this->logFile.write(header, static_cast<std::streamsize>(strlen(header)));
this->logEn = true;
this->timeStart = TIME_NOW();
}
} else {
if (this->logFile.is_open()) {
this->logFile.close();
}
this->logEn = false;
}
}
}
void api_driver::obj::StatisticsLogger::updateCallback(proxy::CpProxy &cp) {
if (!logEn) return;
debug_metrics dm{};
cp.getDebugMetrics(dm);
putItem(dm);
}
void api_driver::obj::StatisticsLogger::putItem(const debug_metrics &item) {
std::lock_guard _lock(this->mutex);
if (!logEn) return;
if (this->logFile.is_open()) {
std::stringstream res;
res << makeTimepointFromMillis(TIME_NOW()) << '\t';
res << item.cnt_ok << '\t';
res << item.cnt_bad << '\t';
res << item.fine_freq_dem << '\t';
res << item.crs_freq_dem << '\t';
res << item.crs_freq_compensator << '\t';
res << item.crs_time_est << '\t';
res << item.fine_time_est << '\t';
res << item.max_level_corr << '\t';
res << item.current_delay << '\t';
res << item.SNR << '\t';
res << item.current_modcod << '\t';
res << item.fine_freq_compensator << '\t';
res << item.ind_freq_grb << '\t';
res << item.ind_freq_tochn << '\t';
res << item.ind_filt_adapt << '\t';
res << item.filter_corr_cinc << '\t';
res << item.corr_cnt << '\t';
res << item.RSS << '\t';
res << item.cor_erl << '\t';
res << item.cor_lat << '\t';
res << item.gc_gain << '\t';
res << item.power_pl_rx << '\n';
const auto out = res.str();
this->logFile.write(out.c_str(), static_cast<std::streamsize>(out.length()));
this->logFile.flush();
}
}
api_driver::obj::StatisticsLogger::~StatisticsLogger() = default;
#endif
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
static int calculateSubnetMask(const std::string& subnet_mask) {
int mask = 0;
std::istringstream iss(subnet_mask);
std::string octet;
while (std::getline(iss, octet, '.')) {
int octet_value = std::stoi(octet);
for (int i = 7; i >= 0; i--) {
if (octet_value & (1 << i)) {
mask++;
}
}
}
return mask;
}
/**
* Преобразует строку вида `1.2.3.4/24` в пару строк вида `1.2.3.4` `255.255.255.0`
*/
std::pair<std::string, std::string> splitIpAndMask(const std::string& input) {
auto pos = input.find('/');
if (pos == std::string::npos) {
// Обработка ошибки: нет символа '/'
throw std::runtime_error("address not contains mask");
}
std::string ip = input.substr(0, pos);
const unsigned int mask_int = std::stoul(input.substr(pos + 1));
if (mask_int > 32) {
throw std::runtime_error("invalid mask");
}
std::string mask_binary = std::string(mask_int, '1') + std::string(32 - mask_int, '0');
std::string mask_str;
for (unsigned int i = 0; i < 4; ++i) {
std::string octet = mask_binary.substr(i * 8u, 8);
int octet_value = std::stoi(octet, nullptr, 2);
mask_str += std::to_string(octet_value) + (i < 3 ? "." : "");
}
return std::make_pair(ip, mask_str);
}
api_driver::obj::TerminalNetworkSettings::TerminalNetworkSettings() { loadDefaults(); }
api_driver::obj::TerminalNetworkSettings::TerminalNetworkSettings(const TerminalNetworkSettings &src) = default;
api_driver::obj::TerminalNetworkSettings & api_driver::obj::TerminalNetworkSettings::operator=(const TerminalNetworkSettings &src) = default;
void api_driver::obj::TerminalNetworkSettings::loadDefaults() {
managementIp = "0.0.0.0/24";
managementGateway = "";
isL2 = true;
dataIp = "0.0.0.0";
dataMtu = 1500;
serverName = DEFAULT_SERVER_NAME;
}
void api_driver::obj::TerminalNetworkSettings::updateCallback(proxy::CpProxy &cp) {
loadDefaults();
try {
managementIp = cp.getNetwork("addr");
managementIp += "/";
managementIp += std::to_string(calculateSubnetMask(cp.getNetwork("mask")));
managementGateway = cp.getNetwork("gateway");
if (cp.getNetwork("mode") == "tun") {
isL2 = false;
dataIp = cp.getNetwork("addr_data");
} else {
isL2 = true;
}
dataMtu = 1500;
serverName = cp.getNetwork("name_serv");
if (serverName.empty()) {
serverName = DEFAULT_SERVER_NAME;
}
} catch (std::exception& e) {
throw std::runtime_error(std::string("api_driver::obj::TerminalNetworkSettings::updateCallback() error: ") + e.what());
}
}
void api_driver::obj::TerminalNetworkSettings::updateFromJson(const nlohmann::json &data) {
managementIp = data.value("managementIp", managementIp);
isL2 = data.value("isL2", isL2);
dataIp = data.value("dataIp", dataIp);
dataMtu = data.value("dataMtu", dataMtu);
serverName = data.value("serverName", serverName);
}
void api_driver::obj::TerminalNetworkSettings::store(proxy::CpProxy& cp) {
try {
cp.setNetwork("mode", isL2 ? "tap" : "tun");
auto [mAddr, mMask] = splitIpAndMask(managementIp);
cp.setNetwork("addr", mAddr);
cp.setNetwork("mask", mMask);
if (!isL2) {
cp.setNetwork("addr_data", dataIp);
}
cp.setNetwork("gateway", managementGateway);
// cp.setNetwork("data_mtu", std::to_string(dataMtu));
cp.setNetwork("name_serv", serverName);
} catch (std::exception& e) {
throw std::runtime_error(std::string("api_driver::obj::TerminalNetworkSettings::store() error: ") + e.what());
}
}
nlohmann::json api_driver::obj::TerminalNetworkSettings::asJson() {
nlohmann::json res;
res["isL2"] = isL2;
res["managementIp"] = managementIp;
res["managementGateway"] = managementGateway;
res["dataIp"] = dataIp;
res["dataMtu"] = dataMtu;
res["serverName"] = serverName;
return res;
}
api_driver::obj::TerminalNetworkSettings::~TerminalNetworkSettings() = default;
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
api_driver::obj::TerminalQosSettings::TerminalQosSettings(): qosSettingsJson(DEFAULT_QOS_CLASSES) {};
api_driver::obj::TerminalQosSettings::TerminalQosSettings(const TerminalQosSettings &src) = default;
api_driver::obj::TerminalQosSettings & api_driver::obj::TerminalQosSettings::operator=(const TerminalQosSettings &src) = default;
void api_driver::obj::TerminalQosSettings::loadDefaults() {
qosEnabled = false;
qosSettingsJson = DEFAULT_QOS_CLASSES;
}
void api_driver::obj::TerminalQosSettings::updateCallback(proxy::CpProxy &cp) {
auto [profile, en] = cp.getQosSettings();
qosEnabled = en;
try {
qosSettingsJson = nlohmann::json::parse(profile);
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "api_driver::obj::TerminalQosSettings::updateCallback(): Failed to parse QoS settings json: " << e.what();
qosSettingsJson = DEFAULT_QOS_CLASSES;
}
}
void api_driver::obj::TerminalQosSettings::updateFromJson(const nlohmann::json &data) {
qosEnabled = data.value("en", qosEnabled);
qosSettingsJson = data.value("profile", qosSettingsJson);
}
void api_driver::obj::TerminalQosSettings::store(proxy::CpProxy &cp) {
cp.setQosSettings(qosSettingsJson.dump(), qosEnabled);
}
nlohmann::json api_driver::obj::TerminalQosSettings::asJson() {
nlohmann::json res;
res["en"] = qosEnabled;
res["profile"] = qosSettingsJson;
return res;
}
api_driver::obj::TerminalQosSettings::~TerminalQosSettings() = default;
#endif
api_driver::obj::TerminalFirmwareVersion::TerminalFirmwareVersion() = default;
api_driver::obj::TerminalFirmwareVersion::TerminalFirmwareVersion(const TerminalFirmwareVersion &src) = default;
api_driver::obj::TerminalFirmwareVersion & api_driver::obj::TerminalFirmwareVersion::operator=(const TerminalFirmwareVersion &src) = default;
void api_driver::obj::TerminalFirmwareVersion::load(proxy::CpProxy &cp) {
version = cp.getNetwork("version");
modemId = cp.getNetwork("chip_id");
rtrim(modemId);
modemSn = cp.getNetwork("serial");
macMang = cp.getNetwork("mac_eth0");
macData = cp.getNetwork("mac_eth1");
}
nlohmann::json api_driver::obj::TerminalFirmwareVersion::asJson() {
nlohmann::json res;
res["version"] = version;
res["modemId"] = modemId;
res["modemSn"] = modemSn;
res["macMang"] = macMang;
res["macData"] = macData;
return res;
}
api_driver::obj::TerminalFirmwareVersion::~TerminalFirmwareVersion() = default;
api_driver::obj::TerminalState::TerminalState() = default;
void api_driver::obj::TerminalState::updateCallback(proxy::CpProxy& cp) {
modulator_state mod{};
modulator_settings modSet{};
demodulator_state demod{};
#ifdef MODEM_IS_SCPC
CinC_state cinc{};
#endif
try {
cp.getModState(mod);
cp.getDemodState(demod);
cp.getModSettings(modSet);
fTxState = modSet.tx_is_on;
fIsTest = modSet.tx_is_on && (!modSet.is_carrier || modSet.is_test_data);
#ifdef MODEM_IS_SCPC
fIsCinC = modSet.is_cinc;
if (fIsCinC) {
cp.getCincState(cinc);
}
#endif
#ifdef MODEM_IS_TDMA
fInitState = cp.getDmaDebug("status_init");
#endif
} catch (std::exception& e) {
throw std::runtime_error(std::string("api_driver::obj::TerminalState::updateCallback() error: ") + e.what());
}
fRxState = demod.locks.sym_sync_lock && demod.locks.freq_lock && demod.locks.afc_lock && demod.locks.pkt_sync;
#ifndef MODEM_IS_SHPS
fRxSymSyncLock = demod.locks.sym_sync_lock;
fRxFreqSearchLock = demod.locks.freq_lock;
fRxAfcLock = demod.locks.afc_lock;
fRxPktSync = demod.locks.pkt_sync;
#endif
fRxSnr = demod.snr;
fRxRssi = demod.rssi;
#ifndef MODEM_IS_SHPS
fRxModcod = demod.modcod;
fRxFrameSizeNormal = !demod.is_short;
fRxIsPilots = demod.is_pilots;
fRxSymError = demod.sym_err;
fRxFreqErr = demod.crs_freq_err;
#endif
fRxFreqErrAcc = demod.fine_freq_err;
fRxInputSignalLevel = demod.if_overload;
fRxPllError = demod.afc_err;
fRxSpeedOnRxKbit = static_cast<double>(demod.speed_in_bytes_rx) / 128.0;
fRxSpeedOnIifKbit = static_cast<double>(demod.speed_in_bytes_rx_iface) / 128.0;
fRxPacketsOk = demod.packet_ok_cnt;
fRxPacketsBad = demod.packet_bad_cnt;
#ifndef MODEM_IS_SHPS
fRxPacketsDummy = demod.dummy_cnt;
fTxModcod = mod.modcod;
#endif
fTxSpeedOnTxKbit = static_cast<double>(mod.speed_in_bytes_tx) / 128.0;
fTxSpeedOnIifKbit = static_cast<double>(mod.speed_in_bytes_tx_iface) / 128.0;
#ifdef MODEM_IS_SCPC
fTxSnr = mod.snr_remote;
fTxFrameSizeNormal = !mod.is_short;
fTxIsPilots = mod.is_pilots;
fCincOcc = cinc.ratio_signal_signal;
fCincCorrelator = cinc.carrier_lock;
fCincCorrelatorFails = cinc.cnt_bad_lock;
fCincFreqErr = cinc.freq_error_offset;
fCincFreqErrAcc = cinc.freq_fine_estimate;
fCincChannelDelay = cinc.delay_dpdi;
#endif
fTxCenterFreq = modSet.central_freq_in_kGz;
fTxSymSpeed = static_cast<double>(modSet.baudrate) / 1000.0;
}
nlohmann::json api_driver::obj::TerminalState::asJson() {
nlohmann::json res{};
res["initState"] = fInitState;
res["testState"] = fIsTest;
#ifdef MODEM_IS_SCPC
res["isCinC"] = fIsCinC;
#endif
res["rx"]["state"] = fRxState;
#ifndef MODEM_IS_SHPS
res["rx"]["sym_sync_lock"] = fRxSymSyncLock;
res["rx"]["freq_search_lock"] = fRxFreqSearchLock;
res["rx"]["afc_lock"] = fRxAfcLock;
res["rx"]["pkt_sync"] = fRxPktSync;
#endif
res["rx"]["snr"] = fRxSnr;
res["rx"]["rssi"] = fRxRssi;
#ifndef MODEM_IS_SHPS
res["rx"]["modcod"] = fRxModcod;
res["rx"]["frameSizeNormal"] = fRxFrameSizeNormal;
res["rx"]["isPilots"] = fRxIsPilots;
res["rx"]["symError"] = fRxSymError;
res["rx"]["freqErr"] = fRxFreqErr;
#endif
res["rx"]["freqErrAcc"] = fRxFreqErrAcc;
res["rx"]["inputSignalLevel"] = fRxInputSignalLevel;
res["rx"]["pllError"] = fRxPllError;
res["rx"]["speedOnRxKbit"] = fRxSpeedOnRxKbit;
res["rx"]["speedOnIifKbit"] = fRxSpeedOnIifKbit;
res["rx"]["packetsOk"] = fRxPacketsOk;
res["rx"]["packetsBad"] = fRxPacketsBad;
#ifndef MODEM_IS_SHPS
res["rx"]["packetsDummy"] = fRxPacketsDummy;
res["tx"]["modcod"] = fTxModcod;
#endif
res["tx"]["state"] = fTxState;
res["tx"]["speedOnTxKbit"] = fTxSpeedOnTxKbit;
res["tx"]["speedOnIifKbit"] = fTxSpeedOnIifKbit;
#ifdef MODEM_IS_SCPC
res["tx"]["snr"] = fTxSnr;
res["tx"]["frameSizeNormal"] = fTxFrameSizeNormal;
res["tx"]["isPilots"] = fTxIsPilots;
if (fIsCinC) {
if (fTxState) {
res["cinc"]["correlator"] = fCincCorrelator;
} else {
res["cinc"]["correlator"] = nullptr;
}
res["cinc"]["occ"] = fCincOcc;
res["cinc"]["correlatorFails"] = fCincCorrelatorFails;
res["cinc"]["freqErr"] = fCincFreqErr;
res["cinc"]["freqErrAcc"] = fCincFreqErrAcc;
res["cinc"]["channelDelay"] = fCincChannelDelay;
} else {
res["cinc"]["correlator"] = nullptr;
}
#endif
res["tx"]["centerFreq"] = fTxCenterFreq;
res["tx"]["symSpeed"] = fTxSymSpeed;
return res;
}
api_driver::obj::TerminalState::~TerminalState() = default;
api_driver::obj::TerminalDeviceState::TerminalDeviceState() = default;
api_driver::obj::TerminalDeviceState::TerminalDeviceState(const TerminalDeviceState &src) = default;
api_driver::obj::TerminalDeviceState & api_driver::obj::TerminalDeviceState::operator=(const TerminalDeviceState &src) = default;
void api_driver::obj::TerminalDeviceState::updateCallback(proxy::CpProxy &cp) {
{
device_state ds{};
cp.getDeviceState(ds);
fTempAdrv = ds.adrv_temp;
fTempZynq = ds.pl_temp;
fTempFpga = ds.zynq_temp;
}
#ifdef MODEM_IS_TDMA
{
progress_msg ds{};
cp.getUpdateStatus(ds);
fUpgradeStatus = ds.status;
fUpgradePercent = ds.dwl_percent;
fUpgradeImage = ds.cur_image;
}
#endif
struct sysinfo info{};
sysinfo(&info);
const double f_load = 100.0 / ((1 << SI_LOAD_SHIFT) * get_nprocs());
fOsUptime = info.uptime;
fOsLoad1 = f_load * static_cast<double>(info.loads[0]);
fOsLoad5 = f_load * static_cast<double>(info.loads[1]);
fOsLoad15 = f_load * static_cast<double>(info.loads[2]);
fOsTotalram = (info.totalram * info.mem_unit) >> 20; // Mb
fOsFreeram = (info.totalram * info.mem_unit) >> 20; // Mb
fOsProcs = info.procs;
}
nlohmann::json api_driver::obj::TerminalDeviceState::asJson() const {
nlohmann::json res;
res["uptime"] = fOsUptime;
res["load1min"] = fOsLoad1;
res["load5min"] = fOsLoad5;
res["load15min"] = fOsLoad15;
res["totalram"] = fOsTotalram;
res["freeram"] = fOsFreeram;
res["procs"] = fOsProcs;
res["adrv"] = fTempAdrv;
res["fpga"] = fTempFpga;
res["zynq"] = fTempZynq;
#ifdef MODEM_IS_TDMA
if (fUpgradeImage.empty()) {
res["upgradeStatus"] = "Нет обновлений";
res["upgradePercent"] = 0;
res["upgradeImage"] = "";
} else {
switch (fUpgradeStatus) {
case NORM_RX_OBJECT_NEW_API: res["upgradeStatus"] = "Начало загрузки"; break;
case NORM_RX_OBJECT_INFO_API: res["upgradeStatus"] = "Получено имя образа"; break;
case NORM_RX_OBJECT_UPDATED_API: res["upgradeStatus"] = "Загружается"; break;
case NORM_RX_OBJECT_COMPLETED_API: res["upgradeStatus"] = "Загрузка завершена"; break;
case NORM_RX_OBJECT_ABORTED_API: res["upgradeStatus"] = "Загрузка прервана"; break;
default: res["upgradeStatus"] = "?";
}
res["upgradePercent"] = fUpgradePercent;
res["upgradeImage"] = fUpgradeImage;
}
#endif
return res;
}
api_driver::obj::TerminalDeviceState::~TerminalDeviceState() = default;
api_driver::obj::TerminalRxTxSettings::TerminalRxTxSettings() = default;
api_driver::obj::TerminalRxTxSettings::TerminalRxTxSettings(const TerminalRxTxSettings &src) = default;
api_driver::obj::TerminalRxTxSettings & api_driver::obj::TerminalRxTxSettings::operator=(const TerminalRxTxSettings &src) = default;
void api_driver::obj::TerminalRxTxSettings::updateCallback(proxy::CpProxy &cp) {
cp.getModSettings(mod);
cp.getDemodSettings(dem);
#ifdef API_STRUCT_ACM_ENABLE
cp.getAcmSettings(acm);
#endif
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
cp.getDpdiSettings(dpdi);
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
cp.getBuclnbSettings(buclnb);
#endif
}
void api_driver::obj::TerminalRxTxSettings::storeMainSettings(proxy::CpProxy &cp) {
cp.setModSettings(mod);
cp.setDemodSettings(dem);
#ifdef API_STRUCT_ACM_ENABLE
cp.setAcmSettings(acm);
#endif
}
struct ModcodDef_t {const char* modulation; const char* speed;};
const static ModcodDef_t ModcodDefs[] = {
{.modulation = "dummy", .speed = "0"},
{.modulation = "qpsk", .speed = "1/4"},
{.modulation = "qpsk", .speed = "1/3"},
{.modulation = "qpsk", .speed = "2/5"},
{.modulation = "qpsk", .speed = "1/2"},
{.modulation = "qpsk", .speed = "3/5"},
{.modulation = "qpsk", .speed = "2/3"},
{.modulation = "qpsk", .speed = "3/4"},
{.modulation = "qpsk", .speed = "4/5"},
{.modulation = "qpsk", .speed = "5/6"},
{.modulation = "qpsk", .speed = "8/9"},
{.modulation = "qpsk", .speed = "9/10"},
{.modulation = "8psk", .speed = "3/5"},
{.modulation = "8psk", .speed = "2/3"},
{.modulation = "8psk", .speed = "3/4"},
{.modulation = "8psk", .speed = "5/6"},
{.modulation = "8psk", .speed = "8/9"},
{.modulation = "8psk", .speed = "9/10"},
{.modulation = "16apsk", .speed = "2/3"},
{.modulation = "16apsk", .speed = "3/4"},
{.modulation = "16apsk", .speed = "4/5"},
{.modulation = "16apsk", .speed = "5/6"},
{.modulation = "16apsk", .speed = "8/9"},
{.modulation = "16apsk", .speed = "9/10"},
{.modulation = "32apsk", .speed = "3/4"},
{.modulation = "32apsk", .speed = "4/5"},
{.modulation = "32apsk", .speed = "5/6"},
{.modulation = "32apsk", .speed = "8/9"},
{.modulation = "32apsk", .speed = "9/10"},
};
static const char* extractModcodModulation(uint32_t modcod, bool defaultQpsk1_4 = true) {
modcod >>= 2;
const auto* d = defaultQpsk1_4 ? ModcodDefs : ModcodDefs + 1;
if (modcod < (sizeof(ModcodDefs) / sizeof(ModcodDef_t))) {
d = ModcodDefs + modcod;
}
return d->modulation;
}
static const char* extractModcodSpeed(uint32_t modcod, bool defaultQpsk1_4 = true) {
modcod >>= 2;
const auto* d = defaultQpsk1_4 ? ModcodDefs : ModcodDefs + 1;
if (modcod < (sizeof(ModcodDefs) / sizeof(ModcodDef_t))) {
d = ModcodDefs + modcod;
}
return d->speed;
}
static bool extractModcodFrameSizeNormal(uint32_t modcod) { return (modcod & 2) == 0; }
static bool extractModcodIsPilots(uint32_t modcod) { return (modcod & 1) != 0; }
static uint32_t buildModcodFromJson(const nlohmann::json& data, uint32_t modcod, const std::string& name, bool isNormalFrame, bool isPilots = false) {
const std::string mod = data.value(name + "Modulation", extractModcodModulation(modcod));
const std::string speed = data.value(name + "Speed", extractModcodSpeed(modcod));
uint32_t _index = 0;
for (const auto& m: ModcodDefs) {
if (mod == m.modulation) {
if (modcod == 0) modcod = _index;
if (speed == m.speed) {
modcod = _index;
break;
}
}
_index++;
}
return (modcod << 2)| (isNormalFrame ? 0 : 2) | (isPilots ? 1 : 0);
}
void api_driver::obj::TerminalRxTxSettings::updateMainSettings(const nlohmann::json &data) {
// для модулятора
#ifdef MODEM_IS_SCPC
mod.is_cinc = data.value("isCinC", mod.is_cinc);
#endif
mod.tx_is_on = data.value("txEn", mod.tx_is_on);
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
mod.is_save_current_state = data.value("txAutoStart", mod.is_save_current_state);
mod.is_test_data = data.value("txIsTestInput", mod.is_test_data);
#endif
mod.is_carrier = !data.value("txModulatorIsTest", !mod.is_carrier);
mod.central_freq_in_kGz = data.value("txCentralFreq", mod.central_freq_in_kGz);
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
mod.baudrate = data.value("txBaudrate", mod.baudrate);
mod.rollof = data.value("txRolloff", mod.rollof);
mod.gold_seq_is_active = data.value("txGoldan", mod.gold_seq_is_active ? 1 : 0);
#endif
mod.attenuation = data.value("txAttenuation", mod.attenuation);
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_SHPS)
bool acmIsFrameSizeNormal = extractModcodFrameSizeNormal(mod.modcod_tx);
bool acmIsPilots = extractModcodIsPilots(mod.modcod_tx);
acmIsFrameSizeNormal = data.value("txFrameSizeNormal", acmIsFrameSizeNormal);
acmIsPilots = data.value("txIsPilots", acmIsPilots);
mod.modcod_tx = buildModcodFromJson(data, mod.modcod_tx, "dvbCcm", acmIsFrameSizeNormal, acmIsPilots);
#endif
#ifdef MODEM_IS_SHPS
mod.koef_spread = data.value("txSpreadCoef", mod.koef_spread);
mod.txFieldsDataPreamble = data.value("txFieldsDataPreamble", mod.txFieldsDataPreamble);
#endif
#ifdef MODEM_IS_TDMA
mod.limitation_attenuation = data.value("txAttenuationLimit", mod.limitation_attenuation);
#endif
// демодулятор
dem.is_aru_on = data.value("rxAgcEn", dem.is_aru_on);
dem.gain = data.value("rxManualGain", dem.gain);
dem.is_rvt_iq = data.value("rxSpectrumInversion", dem.is_rvt_iq);
dem.central_freq_in_kGz = data.value("rxCentralFreq", dem.central_freq_in_kGz);
dem.baudrate = data.value("rxBaudrate", dem.baudrate);
dem.rollof = data.value("rxRolloff", dem.rollof);
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
dem.gold_seq_is_active = data.value("rxGoldan", dem.gold_seq_is_active ? 1 : 0);
#endif
#ifdef MODEM_IS_SHPS
dem.koef_spread = data.value("rxSpreadCoef", dem.koef_spread);
dem.rxFftShift = data.value("rxFftShift", dem.rxFftShift);
dem.rxFieldsDataPreamble = data.value("rxFieldsDataPreamble", dem.rxFieldsDataPreamble);
#endif
#ifdef API_STRUCT_ACM_ENABLE
// ACM
#ifdef MODEM_IS_SCPC
// эти настройки только в SCPC
acm.period_pack_acm = data.value("dvbServicePacketPeriod", acm.period_pack_acm);
acm.enable_acm = data.value("dvbIsAcm", acm.enable_acm);
acm.min_modcod_acm = buildModcodFromJson(data, acm.min_modcod_acm, "dvbAcmMin", acmIsFrameSizeNormal, acmIsPilots);
acm.max_modcod_acm = buildModcodFromJson(data, acm.max_modcod_acm, "dvbAcmMax", acmIsFrameSizeNormal, acmIsPilots);
acm.snr_threashold_acm = data.value("dvbSnrReserve", acm.snr_threashold_acm); // запас ОСШ
#endif
acm.enable_aupc = data.value("aupcEn", acm.enable_aupc);
acm.min_attenuation_aupc = data.value("aupcMinAttenuation", acm.min_attenuation_aupc);
acm.max_attenuation_aupc = data.value("aupcMaxAttenuation", acm.max_attenuation_aupc);
acm.snr_threashold_aupc = data.value("aupcRequiredSnr", acm.snr_threashold_aupc);
#endif
}
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
static double translateCoordinates(uint8_t deg, uint8_t min) {
return static_cast<double>(deg) + static_cast<double>(min) / 60;
}
static std::tuple<uint8_t, uint8_t> translateCoordinates(double abs) {
auto deg = static_cast<uint8_t>(abs);
double min_double = (abs - deg) * 60;
auto min = static_cast<uint8_t>(min_double);
return std::make_tuple(deg, min);
}
void api_driver::obj::TerminalRxTxSettings::updateDpdiSettings(const nlohmann::json &data) {
dpdi.is_delay_window = !data.value("isPositional", !dpdi.is_delay_window);
#ifdef MODEM_IS_SCPC
dpdi.freq_offset = data.value("searchBandwidth", dpdi.freq_offset);
#endif
if (data.contains("positionStationLatitude")) {
const double pos = data["positionStationLatitude"];
const auto [g, m] = translateCoordinates(pos);
dpdi.latitude_station_grad = g;
dpdi.latitude_station_minute = m;
}
if (data.contains("positionStationLongitude")) {
const double pos = data["positionStationLongitude"];
const auto [g, m] = translateCoordinates(pos);
dpdi.longitude_station_grad = g;
dpdi.longitude_station_minute = m;
}
if (data.contains("positionSatelliteLongitude")) {
const double pos = data["positionSatelliteLongitude"];
const auto [g, m] = translateCoordinates(pos);
dpdi.longitude_sattelite_grad = g;
dpdi.longitude_sattelite_minute = m;
}
#ifdef MODEM_IS_SCPC
dpdi.min_delay = data.value("delayMin", dpdi.min_delay);
dpdi.max_delay = data.value("delayMax", dpdi.max_delay);
#else
dpdi.min_delay = 0;
dpdi.max_delay = data.value("delay", dpdi.max_delay);
#endif
}
void api_driver::obj::TerminalRxTxSettings::storeDpdiSettings(proxy::CpProxy &cp) {
cp.setDpdiSettings(dpdi);
}
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
void api_driver::obj::TerminalRxTxSettings::updateBuclnbSettings(const nlohmann::json &data) {
{
// напряжение buc
int oldVoltage = 0;
if (buclnb.buc == voltage_buc::_24V) { oldVoltage = 24; }
#ifdef MODEM_IS_SCPC
if (buclnb.buc == voltage_buc::_24V) { oldVoltage = 24; }
#endif
auto result = data.value("bucPowering", oldVoltage);
switch (result) {
case 24: buclnb.buc = voltage_buc::_24V; break;
#ifdef MODEM_IS_SCPC
case 48: buclnb.buc = voltage_buc::_48V; break;
#endif
case 0:
default:
buclnb.buc = voltage_buc::DISABLE;
}
}
buclnb.is_ref_10MHz_buc = data.value("bucRefClk10M", buclnb.is_ref_10MHz_buc);
#ifdef MODEM_IS_TDMA
buclnb.lo_buc_inkHz = data.value("bucLoKhz", buclnb.lo_buc_inkHz);
#endif
{
// напряжение lnb
int oldVoltage;
switch (buclnb.lnb) {
case voltage_lnb::_13V: oldVoltage = 13; break;
case voltage_lnb::_18V: oldVoltage = 18; break;
case voltage_lnb::_24V: oldVoltage = 24; break;
default: oldVoltage = 0;
}
auto result = data.value("lnbPowering", oldVoltage);
switch (result) {
case 13: buclnb.lnb = voltage_lnb::_13V; break;
case 18: buclnb.lnb = voltage_lnb::_18V; break;
case 24: buclnb.lnb = voltage_lnb::_24V; break;
default: buclnb.lnb = voltage_lnb::DISABLE;
}
}
buclnb.is_ref_10MHz_lnb = data.value("lnbRefClk10M", buclnb.is_ref_10MHz_lnb);
#ifdef MODEM_IS_TDMA
buclnb.lo_lnb_inkHz = data.value("lnbLoKhz", buclnb.lo_lnb_inkHz);
#endif
buclnb.is_ref_10MHz_output = data.value("srvRefClk10M", buclnb.is_ref_10MHz_output);
buclnb.is_save_current_state = data.value("bucLnbAutoStart", buclnb.is_save_current_state);
}
void api_driver::obj::TerminalRxTxSettings::storeBuclnbSettings(proxy::CpProxy &cp) {
cp.setBuclnbSettings(buclnb);
}
#endif
void api_driver::obj::TerminalRxTxSettings::storeAll(proxy::CpProxy &cp) {
storeMainSettings(cp);
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
storeDpdiSettings(cp);
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
storeBuclnbSettings(cp);
#endif
}
nlohmann::json api_driver::obj::TerminalRxTxSettings::asJson() const {
nlohmann::json res;
// RX TX
{
auto& rxtx = res["rxtx"];
// для модулятора
#ifdef MODEM_IS_SCPC
rxtx["isCinC"] = mod.is_cinc;
#endif
rxtx["txEn"] = mod.tx_is_on;
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
rxtx["txAutoStart"] = mod.is_save_current_state;
rxtx["txIsTestInput"] = mod.is_test_data;
#endif
rxtx["txModulatorIsTest"] = !mod.is_carrier;
rxtx["txCentralFreq"] = mod.central_freq_in_kGz;
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
rxtx["txBaudrate"] = mod.baudrate;
rxtx["txRolloff"] = mod.rollof;
rxtx["txGoldan"] = mod.gold_seq_is_active ? 1 : 0;
#endif
rxtx["txAttenuation"] = mod.attenuation;
#ifdef MODEM_IS_SHPS
rxtx["txSpreadCoef"] = mod.koef_spread;
rxtx["txFieldsDataPreamble"] = mod.txFieldsDataPreamble;
#endif
#if defined(MODEM_IS_SCPC) || defined(MODEM_IS_SHPS)
const bool acmIsFrameSizeNormal = extractModcodFrameSizeNormal(mod.modcod_tx);
const bool acmIsPilots = extractModcodIsPilots(mod.modcod_tx);
rxtx["txFrameSizeNormal"] = acmIsFrameSizeNormal;
rxtx["txIsPilots"] = acmIsPilots;
rxtx["dvbCcmModulation"] = extractModcodModulation(mod.modcod_tx);
rxtx["dvbCcmSpeed"] = extractModcodSpeed(mod.modcod_tx);
#endif
#ifdef MODEM_IS_TDMA
rxtx["txAttenuationLimit"] = mod.limitation_attenuation;
#endif
// демодулятор
rxtx["rxAgcEn"] = dem.is_aru_on;
rxtx["rxManualGain"] = dem.gain;
rxtx["rxSpectrumInversion"] = dem.is_rvt_iq;
rxtx["rxCentralFreq"] = dem.central_freq_in_kGz;
rxtx["rxBaudrate"] = dem.baudrate;
rxtx["rxRolloff"] = dem.rollof;
#if defined(MODEM_IS_SCPC) || defined (MODEM_IS_SHPS)
rxtx["rxGoldan"] = dem.gold_seq_is_active ? 1 : 0;
#endif
#ifdef MODEM_IS_SHPS
rxtx["rxSpreadCoef"] = dem.koef_spread;
rxtx["rxFftShift"] = dem.rxFftShift;
rxtx["rxFieldsDataPreamble"] = dem.rxFieldsDataPreamble;
#endif
#ifdef API_STRUCT_ACM_ENABLE
// ACM
#ifdef MODEM_IS_SCPC
// эти настройки только в SCPC
rxtx["dvbServicePacketPeriod"] = acm.period_pack_acm;
rxtx["dvbIsAcm"] = acm.enable_acm;
rxtx["dvbAcmMinModulation"] = extractModcodModulation(acm.min_modcod_acm);
rxtx["dvbAcmMinSpeed"] = extractModcodSpeed(acm.min_modcod_acm);
rxtx["dvbAcmMaxModulation"] = extractModcodModulation(acm.max_modcod_acm);
rxtx["dvbAcmMaxSpeed"] = extractModcodSpeed(acm.max_modcod_acm);
rxtx["dvbSnrReserve"] = acm.snr_threashold_acm; // запас ОСШ
#endif
rxtx["aupcEn"] = acm.enable_aupc;
rxtx["aupcMinAttenuation"] = acm.min_attenuation_aupc;
rxtx["aupcMaxAttenuation"] = acm.max_attenuation_aupc;
rxtx["aupcRequiredSnr"] = acm.snr_threashold_aupc;
#endif
}
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
{
auto& dp = res["dpdi"];
dp["isPositional"] = !dpdi.is_delay_window;
#ifdef MODEM_IS_SCPC
dp["searchBandwidth"] = dpdi.freq_offset;
#endif
dp["positionStationLatitude"] = translateCoordinates(dpdi.latitude_station_grad, dpdi.latitude_station_minute);
dp["positionStationLongitude"] = translateCoordinates(dpdi.longitude_station_grad, dpdi.longitude_station_minute);
dp["positionSatelliteLongitude"] = translateCoordinates(dpdi.longitude_sattelite_grad, dpdi.longitude_sattelite_minute);
#ifdef MODEM_IS_SCPC
dp["delayMin"] = dpdi.min_delay;
dp["delayMax"] = dpdi.max_delay;
#else
dp["delay"] = dpdi.max_delay;
#endif
}
#endif
// BucLnb
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
{
auto& bl = res["buclnb"];
switch (buclnb.buc) {
case voltage_buc::_24V: bl["bucPowering"] = 24; break;
#ifdef MODEM_IS_SCPC
case voltage_buc::_48V: bl["bucPowering"] = 48; break;
#endif
default: bl["bucPowering"] = 0;
}
#ifdef MODEM_IS_TDMA
bl["bucLoKhz"] = buclnb.lo_buc_inkHz;
#endif
bl["bucRefClk10M"] = buclnb.is_ref_10MHz_buc;
switch (buclnb.lnb) {
case voltage_lnb::_13V: bl["lnbPowering"] = 13; break;
case voltage_lnb::_18V: bl["lnbPowering"] = 18; break;
case voltage_lnb::_24V: bl["lnbPowering"] = 24; break;
default: bl["lnbPowering"] = 0;
}
bl["lnbRefClk10M"] = buclnb.is_ref_10MHz_lnb;
#ifdef MODEM_IS_TDMA
bl["lnbLoKhz"] = buclnb.lo_lnb_inkHz;
#endif
bl["srvRefClk10M"] = buclnb.is_ref_10MHz_output;
bl["bucLnbAutoStart"] = buclnb.is_save_current_state;
}
#endif
return res;
}
api_driver::obj::TerminalRxTxSettings::~TerminalRxTxSettings() = default;

260
src/api-driver/structs.h Normal file
View File

@@ -0,0 +1,260 @@
#ifndef STRUCTS_H
#define STRUCTS_H
#include "api-driver/stricts-enable.h"
#include "proxy.h"
#include <atomic>
#include <fstream>
#include <shared_mutex>
#include <string>
#include "common/nlohmann/json.hpp"
namespace api_driver::obj {
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
class StatisticsLogger {
public:
StatisticsLogger();
static constexpr const char* LOG_FILENAME = "/tmp/weblog-statistics.csv";
int64_t timeStart;
bool logEn = false;
std::atomic<int> logPeriodMs = 1000;
std::atomic<int> maxAgeMs = 10000;
/**
*
* @return {"en": bool, "logPeriodMs": int, "maxAgeMs": int}
*/
nlohmann::json getSettings();
void setSettings(const nlohmann::json& data);
void updateCallback(proxy::CpProxy& cp);
/**
* Записать значение в "базу данных". Метку при этом вставлять не нужно, она будет вставлена автоматически.
* @param item
*/
void putItem(const debug_metrics& item);
// void collectExpiredItems();
// void resetLogs() {
// std::lock_guard _lock(mutex);
// logs.clear();
// }
~StatisticsLogger();
private:
// std::pmr::deque<LogItem> logs;
std::fstream logFile{};
std::shared_mutex mutex;
};
#endif
#if defined(MODEM_IS_SCPC)
static constexpr const char* DEFAULT_SERVER_NAME = "RSCM-101";
#elif defined(MODEM_IS_TDMA)
static constexpr const char* DEFAULT_SERVER_NAME = "TDMA Abonent";
#elif defined(MODEM_IS_SHPS)
static constexpr const char* DEFAULT_SERVER_NAME = "SHPS Terminal";
#else
#error "Selected modem type not supported!"
#endif
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
class TerminalNetworkSettings {
public:
std::string managementIp, managementGateway, dataIp, serverName;
bool isL2 = true;
unsigned int dataMtu = 1500;
TerminalNetworkSettings();
TerminalNetworkSettings(const TerminalNetworkSettings& src);
TerminalNetworkSettings& operator= (const TerminalNetworkSettings& src);
void loadDefaults();
void updateCallback(proxy::CpProxy& cp);
void updateFromJson(const nlohmann::json& data);
void store(proxy::CpProxy& cp);
nlohmann::json asJson();
~TerminalNetworkSettings();
};
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
class TerminalQosSettings {
public:
static constexpr const char* DEFAULT_QOS_CLASSES = R"({"rt1":[],"rt2":[],"rt3":[],"cd":[]})";
nlohmann::json qosSettingsJson;
bool qosEnabled = false;
TerminalQosSettings();
TerminalQosSettings(const TerminalQosSettings& src);
TerminalQosSettings& operator= (const TerminalQosSettings& src);
void loadDefaults();
void updateCallback(proxy::CpProxy& cp);
void updateFromJson(const nlohmann::json& data);
void store(proxy::CpProxy& cp);
nlohmann::json asJson();
~TerminalQosSettings();
};
#endif
class TerminalFirmwareVersion {
public:
std::string version, modemId, modemSn, macMang, macData;
TerminalFirmwareVersion();
TerminalFirmwareVersion(const TerminalFirmwareVersion& src);
TerminalFirmwareVersion& operator= (const TerminalFirmwareVersion& src);
void load(proxy::CpProxy& cp);
nlohmann::json asJson();
~TerminalFirmwareVersion();
};
/**
* Обертка состояния терминала, тут состояние девайса, модулятора/демодулятора, состояние автоматического обновления.
*/
class TerminalState {
public:
std::string fInitState{};
bool fIsTest = false; // daemon->isTest()
bool fTxState = false;
#ifdef MODEM_IS_SCPC
bool fIsCinC = false;
#endif
bool fRxState{};
#ifndef MODEM_IS_SHPS
bool fRxSymSyncLock{};
bool fRxFreqSearchLock{};
bool fRxAfcLock{};
bool fRxPktSync{};
#endif
float fRxSnr{};
float fRxRssi{};
#ifndef MODEM_IS_SHPS
uint16_t fRxModcod{};
bool fRxFrameSizeNormal{};
bool fRxIsPilots{};
double fRxSymError{};
double fRxFreqErr{};
#endif
double fRxFreqErrAcc{};
double fRxInputSignalLevel{};
double fRxPllError{};
double fRxSpeedOnRxKbit{};
double fRxSpeedOnIifKbit{};
uint32_t fRxPacketsOk{};
uint32_t fRxPacketsBad{};
#ifndef MODEM_IS_SHPS
uint32_t fRxPacketsDummy{};
uint16_t fTxModcod{};
#endif
double fTxSpeedOnTxKbit{};
double fTxSpeedOnIifKbit{};
#ifdef MODEM_IS_SCPC
float fTxSnr{};
bool fTxFrameSizeNormal{};
bool fTxIsPilots{};
double fCincOcc{};
bool fCincCorrelator{};
uint32_t fCincCorrelatorFails{};
int32_t fCincFreqErr{};
int32_t fCincFreqErrAcc{};
float fCincChannelDelay{};
#endif
double fTxCenterFreq;
double fTxSymSpeed;
TerminalState();
/**
* Обновление основной части статистики, то есть RX/TX и sysinfo
*/
void updateCallback(proxy::CpProxy& cp);
nlohmann::json asJson();
~TerminalState();
};
class TerminalDeviceState {
public:
time_t fOsUptime{};
double fOsLoad1{};
double fOsLoad5{};
double fOsLoad15{};
unsigned int fOsTotalram{};
unsigned int fOsFreeram{};
unsigned int fOsProcs{};
double fTempAdrv{};
double fTempZynq{};
double fTempFpga{};
#ifdef MODEM_IS_TDMA
DOWNLOAD_STATUS fUpgradeStatus{};
unsigned int fUpgradePercent{};
std::string fUpgradeImage;
#endif
TerminalDeviceState();
TerminalDeviceState(const TerminalDeviceState& src);
TerminalDeviceState& operator= (const TerminalDeviceState& src);
void updateCallback(proxy::CpProxy& cp);
nlohmann::json asJson() const;
~TerminalDeviceState();
};
class TerminalRxTxSettings {
modulator_settings mod{};
demodulator_settings dem{};
#ifdef API_STRUCT_ACM_ENABLE
ACM_parameters_serv_ acm{};
#endif
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
DPDI_parmeters dpdi{};
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
buc_lnb_settings buclnb{};
#endif
public:
TerminalRxTxSettings();
TerminalRxTxSettings(const TerminalRxTxSettings& src);
TerminalRxTxSettings& operator= (const TerminalRxTxSettings& src);
void updateCallback(proxy::CpProxy& cp);
void updateMainSettings(const nlohmann::json& data);
void storeMainSettings(proxy::CpProxy& cp);
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
void updateDpdiSettings(const nlohmann::json& data);
void storeDpdiSettings(proxy::CpProxy& cp);
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
void updateBuclnbSettings(const nlohmann::json& data);
void storeBuclnbSettings(proxy::CpProxy& cp);
#endif
void storeAll(proxy::CpProxy& cp);
nlohmann::json asJson() const;
~TerminalRxTxSettings();
};
}
#endif //STRUCTS_H

118
src/auth/jwt.cpp Normal file
View File

@@ -0,0 +1,118 @@
#include "jwt.h"
#include <random>
#include "utils.h"
#include <sys/time.h>
static int64_t milliseconds() {
timeval tv{};
gettimeofday(&tv,nullptr);
return ((tv.tv_sec * 1000000l) + tv.tv_usec) / 1000;
}
std::string http::auth::jwt::secretKey;
void http::auth::jwt::generateSecretKey() {
secretKey.clear();
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<> distribution('!', '~');
for (size_t i = 0; i < 32; ++i) {
secretKey += static_cast<char>(distribution(generator));
}
}
http::auth::jwt::Jwt http::auth::jwt::Jwt::fromCookies(const std::string &cookie) {
auto pc = utils::parseCookies(cookie);
Jwt t;
if (pc.find("auth") != pc.end()) {
const auto auth = pc.at("auth");
std::string::size_type firstDot = std::string::npos;
std::string::size_type secondDot = std::string::npos;
for (std::string::size_type i = 0; i < auth.size(); i++) {
if (auth[i] == '.') {
if (firstDot == std::string::npos) { firstDot = i; }
else if (secondDot == std::string::npos) { secondDot = i; }
else {
// так быть не должно
return t;
}
}
}
if (firstDot == std::string::npos || secondDot == std::string::npos || secondDot - firstDot == 0) {
// так тоже быть не должно
return t;
}
try {
t.payload = auth.substr(0, firstDot);
t.signature = auth.substr(secondDot + 1);
t.lastUpdate = std::stol(auth.substr(firstDot + 1, secondDot - firstDot - 1));
// теперь проверим, что сигнатура верная, только тогда декодируем строку юзера
auto realSignature = utils::sha256(t.payload + std::to_string(t.lastUpdate) + secretKey);
if (t.signature != realSignature) {
t.payload.clear();
} else {
t.payload = utils::b64Decode(t.payload);
}
} catch (std::exception& e) {
t.payload.clear();
t.lastUpdate = 0;
t.signature.clear();
}
}
return t;
}
http::auth::jwt::Jwt http::auth::jwt::Jwt::fromUser(const std::string &user) {
Jwt t;
t.payload = user;
t.lastUpdate = milliseconds();
return t;
}
bool http::auth::jwt::Jwt::isValid() {
if (payload.empty() || signature.empty()) {
return false;
}
// проверка сигнатуры не нужна, она была на стадии парсинга куки
// auto realSignature = utils::sha256(utils::b64Encode(this->payload) + std::to_string(this->lastUpdate) + secretKey);
// if (signature != realSignature) {
// return false;
// }
const auto currTime = milliseconds();
return currTime <= lastUpdate + SESSION_LIVE_MS && currTime >= lastUpdate;
}
std::string http::auth::jwt::Jwt::getUsername() {
return payload;
}
std::string http::auth::jwt::Jwt::asCookie(bool isSecure) {
this->lastUpdate = milliseconds();
const auto uTime = std::to_string(this->lastUpdate);
const auto encodedPayload = utils::b64Encode(payload);
signature = utils::sha256(encodedPayload + uTime + secretKey);
const auto val = encodedPayload + "." + uTime + "." + signature;
std::string cookie = "auth=";
cookie += val;
cookie += ";Path=/; Max-Age=";
cookie += std::to_string(SESSION_LIVE_MS / 1000);
if (isSecure) {
cookie += "; Secure";
}
cookie += "; HttpOnly; SameSite=Lax";
return cookie;
}
bool http::auth::jwt::Jwt::needUpdate() const {
return milliseconds() >= lastUpdate + SESSION_UPDATE_THRESHOLD;
}
http::auth::jwt::Jwt::~Jwt() = default;

41
src/auth/jwt.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef JWT_H
#define JWT_H
#include <string>
#include "resources.h"
namespace http::auth::jwt {
extern std::string secretKey;
constexpr const char* EMPTY_AUTH_COOKIE = "auth=;Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";
constexpr int64_t SESSION_LIVE_MS = 24 * 60 * 60 * 1000; // 24 часа
constexpr int64_t SESSION_UPDATE_THRESHOLD = 10 * 60 * 1000; // 10 минут
void generateSecretKey();
/**
* Упрощенная реализация JWT (Json Web Token). Токен имеет вид: `{ username | base64 }.{ signature | base64 }`.
* Сигнатура вычисляется следующим образом: `SHA256(username + secret)`.
* Имя cookie: `auth`.
*/
class Jwt {
std::string payload;
int64_t lastUpdate = 0;
std::string signature;
public:
static Jwt fromCookies(const std::string& cookie);
static Jwt fromUser(const std::string& User);
bool isValid();
std::string getUsername();
std::string asCookie(bool isSecure = false);
bool needUpdate() const;
~Jwt();
};
}
#endif //JWT_H

View File

@@ -1,5 +1,102 @@
//
// Created by vlad on 31.10.2024.
//
#include "resources.h"
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string.hpp>
#include <utility>
#include "jwt.h"
#include "utils.h"
http::auth::User::User(const std::string &username, const std::string &passwordHash): username(username),
passwordHash(passwordHash.empty() ? utils::sha256(username) : passwordHash) {}
http::auth::User::User(const std::string &username, const std::string &passwordHash, uint32_t perms): perms(perms),
username(username), passwordHash(passwordHash.empty() ? utils::sha256(username) : passwordHash) {}
bool http::auth::User::checkPassword(const std::string &pass) const {
return utils::sha256(pass) == passwordHash;
}
void http::auth::User::setPassword(const std::string &pass) {
this->passwordHash = utils::sha256(pass);
}
bool http::auth::User::checkPremisions(uint32_t p) const {
if (this->perms & SUPERUSER) {
return true;
}
return (this->perms & p) == p;
}
void http::auth::User::setPremisions(uint32_t p) {
if (p & SUPERUSER) {
this->perms = SUPERUSER;
} else {
this->perms |= p;
}
}
void http::auth::User::resetPremisions(uint32_t p) {
this->perms &= p;
}
http::auth::User::~User() = default;
http::auth::AuthProvider::AuthProvider() = default;
std::shared_ptr<http::auth::User> http::auth::AuthProvider::doAuth(const std::string &username, const std::string &password, const server::Request &req, server::Reply &rep) {
for (const auto& u: users) {
if (u->username == username) {
if (u->checkPassword(password)) {
auto t = jwt::Jwt::fromUser(u->username);
rep.headers.push_back({.name = "Set-Cookie", .value = t.asCookie(req.isSecure)});
return u;
}
BOOST_LOG_TRIVIAL(warning) << "http::auth::AuthProvider::doAuth(): Failed to login " << username << ", password: " << password << " (incorrect password)";
return nullptr;
}
}
BOOST_LOG_TRIVIAL(warning) << "http::auth::AuthProvider::doAuth(): Failed to login " << username << ", password: " << password << " (user not found)";
return nullptr;
}
std::shared_ptr<http::auth::User> http::auth::AuthProvider::getSession(const server::Request &req, server::Reply &rep) {
auto t = jwt::Jwt::fromCookies(req.getHeaderValue("cookie"));
if (t.isValid()) {
const auto name = t.getUsername();
// токен валидный, ищем юзера
for (auto& u: users) {
if (u->username == name) {
// на всякий случай тут проверяем, что токен пора обновлять
if (t.needUpdate()) {
rep.headers.push_back({.name = "Set-Cookie", .value = t.asCookie(req.isSecure)});
}
return u;
}
}
BOOST_LOG_TRIVIAL(warning) << "http::auth::AuthProvider::getSession(): Found valid session for a non-existent user " << name;
}
return nullptr;
}
http::auth::AuthProvider::~AuthProvider() = default;
http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path, AuthProvider& provider, resource::respGenerator generator):
BasicResource(path), provider_(provider), generator_(std::move(generator)), perms(User::SUPERUSER) {}
http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path, AuthProvider& provider, uint32_t perms, resource::respGenerator generator):
BasicResource(path), provider_(provider), generator_(std::move(generator)), perms(perms) {}
void http::auth::AuthRequiredResource::handle(const server::Request &req, server::Reply &rep) {
if (auto user = this->provider_.getSession(req, rep)) {
if (user->checkPremisions(this->perms)) {
this->generator_(req, rep);
return;
}
stockReply(server::forbidden, rep);
} else {
stockReply(server::unauthorized, rep);
}
}
http::auth::AuthRequiredResource::~AuthRequiredResource() = default;

View File

@@ -4,25 +4,108 @@
namespace http::auth {
/**
* Класс пользовательских разрешений,
*/
class UserPremision{};
* Класс пользователя, содержит логин/хеш_пароля/настройки пользователя/права.
* Хеш пароля представляется в виде строки
*/
class User {
private:
uint32_t perms{};
/**
* Класс пользователя, содержит логин/хеш_пароля/настройки пользователя/права
*/
class User{};
public:
const std::string username;
std::string passwordHash;
/**
* Класс аутентификации. Управляет всеми сессиями, создает новые при логине, удаляет при логауте.
* @note Класс устанавливает заголовок 'Set-Cookie' в ответе, и этот заголовок должен дойти до пользователя!
*/
class AuthProvider {
/**
* Конструктор пользователя. По-умолчанию пользователь создается без прав, их нужно задать отдельной функцией.
* @param username Имя пользователя, он же - логин.
* @param passwordHash Хеш sha256 пароля пользователя. Если передать пустой, то пароль будет сгенерирован такой же, как и имя пользователя.
*/
explicit User(const std::string& username, const std::string& passwordHash = "");
/**
* Конструктор пользователя с указанными правами. Аналогично первому конструктору.
* @param username Имя пользователя, он же - логин.
* @param passwordHash Хеш sha256 пароля пользователя. Если передать пустой, то пароль будет сгенерирован такой же, как и имя пользователя.
* @param perms Права пользователя
*/
explicit User(const std::string& username, const std::string& passwordHash, uint32_t perms);
/**
* Проверить пароль на соответствие хешу
* @param pass
* @return
*/
bool checkPassword(const std::string& pass) const;
/**
* Установка пароля
* @param pass строка с исходным паролем
*/
void setPassword(const std::string& pass);
static constexpr uint32_t SUPERUSER = 0x0001;
static constexpr uint32_t WATCH_STATISTICS = 0x0002; // мониторинг модема
static constexpr uint32_t RESET_PACKET_STATISTICS = 0x0004; // сброс статистики пакетов
static constexpr uint32_t WATCH_SETTINGS = 0x0008; // просмотр настроек , если недоступно, то вкладки с настройками не будет
static constexpr uint32_t EDIT_SETTINGS = 0x0010; // редактирование настроек, установка параметров модулятора/демодулятора/dma/cinc
static constexpr uint32_t UPDATE_FIRMWARE = 0x0020; // обновление прошивки
static constexpr uint32_t SETUP_QOS = 0x0040; // управление профилем QoS
static constexpr uint32_t DEVELOPER = 0x0080; // использование функций для разработчиков
/**
* Проверить, что у пользователя есть нужное право. Если это суперпользователь, то у него по умолчанию все права есть.
* @param p набор прав, из констант данного класса.
* @return
*/
bool checkPremisions(uint32_t p) const;
void setPremisions(uint32_t p);
void resetPremisions(uint32_t p);
~User();
};
class NeedAuentificationResource: public resource::BasicResource {};
/**
* Класс аутентификации. Управляет всеми сессиями, создает новые при логине, удаляет при логауте.
* @note Класс устанавливает заголовок 'Set-Cookie' в ответе, и этот заголовок должен дойти до пользователя!
*/
class AuthProvider {
void updateSessionHook();
public:
AuthProvider();
/**
* Авторизовать пользователя.
*
* @param req
* @param rep
* @return true, в случае успешной авторизации
*/
std::shared_ptr<http::auth::User> doAuth(const std::string &username, const std::string &password, const server::Request &req, server::Reply &rep);
std::vector<std::shared_ptr<User>> users;
std::shared_ptr<User> getSession(const server::Request &req, server::Reply &rep);
~AuthProvider();
};
class AuthRequiredResource final: public resource::BasicResource {
public:
explicit AuthRequiredResource(const std::string& path, AuthProvider& provider, resource::respGenerator generator);
explicit AuthRequiredResource(const std::string& path, AuthProvider& provider, uint32_t perms, resource::respGenerator generator);
void handle(const server::Request &req, server::Reply &rep) override;
~AuthRequiredResource() override;
private:
AuthProvider& provider_;
resource::respGenerator generator_;
uint32_t perms;
};
}
#endif //RESOURCES_H

141
src/auth/utils.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "utils.h"
#include <cassert>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <string>
#include <iomanip>
std::string http::utils::sha256(const std::string &payload) {
return sha256(payload.c_str(), payload.size());
}
std::string http::utils::sha256(const char* data, size_t size) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char *>(data), size, hash);
// Преобразуем хеш в шестнадцатеричную строку
std::stringstream ss;
for (unsigned char i : hash) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(i);
}
return ss.str();
}
inline std::string sha256AsB64(const std::string &payload) {
// Вычисляем SHA256 хеш
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length(), hash);
return http::utils::b64Encode(reinterpret_cast<const char *>(hash), SHA256_DIGEST_LENGTH);
}
static const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string http::utils::b64Encode(const char* data, size_t size) {
using ::std::string;
// Use = signs so the end is properly padded.
string retval((((size + 2) / 3) * 4), '=');
::std::size_t outpos = 0;
int bits_collected = 0;
unsigned int accumulator = 0;
for (size_t index = 0; index < size; index++) {
const char i = *(data + index);
accumulator = (accumulator << 8) | (i & 0xff);
bits_collected += 8;
while (bits_collected >= 6) {
bits_collected -= 6;
retval[outpos++] = b64_table[(accumulator >> bits_collected) & 0x3fu];
}
}
if (bits_collected > 0) {
// Any trailing bits that are missing.
assert(bits_collected < 6);
accumulator <<= 6 - bits_collected;
retval[outpos++] = b64_table[accumulator & 0x3fu];
}
assert(outpos >= (retval.size() - 2));
assert(outpos <= retval.size());
return retval;
}
std::string http::utils::b64Encode(const std::string &payload) {
return b64Encode(payload.c_str(), payload.size());
}
std::string http::utils::b64Decode(const std::string &ascdata) {
static const unsigned char reverse_table[128] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64
};
using ::std::string;
string retval;
int bits_collected = 0;
unsigned int accumulator = 0;
for (const auto cd: ascdata) {
const auto c = static_cast<int>(cd);
if (::std::isspace(c) || c == '=') {
// Skip whitespace and padding. Be liberal in what you accept.
continue;
}
if ((c > 127) || (c < 0) || (reverse_table[c] > 63)) {
throw ::std::invalid_argument("This contains characters not legal in a base64 encoded string.");
}
accumulator = (accumulator << 6) | reverse_table[c];
bits_collected += 6;
if (bits_collected >= 8) {
bits_collected -= 8;
retval += static_cast<char>((accumulator >> bits_collected) & 0xffu);
}
}
return retval;
}
std::map<std::string, std::string> http::utils::parseCookies(const std::string& cookieString) {
std::map<std::string, std::string> cookies;
std::istringstream cookieStream(cookieString);
std::string cookie;
while (std::getline(cookieStream, cookie, ';')) {
// Разделяем имя и значение Cookie
size_t equalPos = cookie.find('=');
if (equalPos == std::string::npos) {
continue; // Неверный формат Cookie
}
size_t startIndex = 0;
while (startIndex < cookie.size()) {
if (cookie[startIndex] == '=') {
// некорректная кука, состоит только из пробелов, так что на этом обработку и закончим
return cookies;
}
if (cookie[startIndex] == ' ') {
startIndex++;
} else {
break;
}
}
std::string name = cookie.substr(startIndex, equalPos - startIndex);
std::string value = cookie.substr(equalPos + 1);
// Удаляем пробелы с начала и конца значения Cookie
value.erase(0, value.find_first_not_of(' '));
value.erase(value.find_last_not_of(' ') + 1);
// Добавляем Cookie в map
cookies[name] = value;
}
return cookies;
}

19
src/auth/utils.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef UTILS_H
#define UTILS_H
#include <map>
#include <string>
namespace http::utils {
std::string sha256(const std::string& payload);
std::string sha256(const char* data, size_t size);
std::string sha256AsB64(const std::string& payload);
std::string b64Encode(const char* data, size_t size);
std::string b64Encode(const std::string& payload);
std::string b64Decode(const std::string& payload);
std::map<std::string, std::string> parseCookies(const std::string& cookieStrin);
}
#endif //UTILS_H

24765
src/common/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,28 +16,18 @@
#include <fstream>
#include "terminal_api_driver.h"
#include "version.h"
#include "auth/resources.h"
#include "auth/jwt.h"
#include "auth/utils.h"
#include "common/nlohmann/json.hpp"
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
static std::vector<char> loadFile(const std::string& path) {
std::ifstream is(path, std::ios::in | std::ios::binary);
if (!is) {
throw std::runtime_error("File not found");
}
std::vector<char> content;
for (;;) {
char buf[512];
auto len = is.read(buf, sizeof(buf)).gcount();
if (len <= 0) {
break;
}
content.insert(content.end(), buf, buf + len);
}
return content;
}
constexpr const char* REBOOT_COMMAND = "web-action reboot";
constexpr const char* UPGRADE_COMMAND = "web-action upgrade";
constexpr const char* FIRMWARE_LOCATION = "/tmp/firmware.zip";
namespace mime_types = http::server::mime_types;
@@ -50,64 +40,640 @@ void init_logging() {
log::register_simple_formatter_factory<log::trivial::severity_level, char>("Severity");
// #ifdef USE_DEBUG
// log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]");
// #else
// log::add_file_log(
// keywords::file_name = "/home/root/manager_orlik_%N.log",
// keywords::rotation_size = 10 * 1024 * 1024,
// keywords::time_based_rotation = log::sinks::file::rotation_at_time_point(0, 0, 0),
// keywords::format = expressions::format("%1% [%2%] [%3%] <%4%> [%5%]")
// % expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d, %H:%M:%S.%f")
// % expressions::format_named_scope("Scope", keywords::format = "%n (%f:%l)")
// % expressions::attr<log::trivial::severity_level>("Severity")
// % expressions::message % expressions::attr<attributes::current_thread_id::value_type>("ThreadID"),
// keywords::open_mode = std::ios_base::app,
// keywords::auto_flush = true
// );
// #endif
log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]");
#ifdef USE_DEBUG
log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message%");
#else
log::add_file_log(
keywords::file_name = "http_server_%N.log",
keywords::rotation_size = 10 * 1024 * 1024,
keywords::time_based_rotation = log::sinks::file::rotation_at_time_point(0, 0, 0),
keywords::format = "%TimeStamp%: [%Severity%] %Message%",
keywords::open_mode = std::ios_base::app,
keywords::auto_flush = true
);
#endif
log::core::get()->set_filter(log::trivial::severity >= log::trivial::info);
log::add_common_attributes();
}
static void initResources(http::server::Server& s, std::shared_ptr<api_driver::ApiDriver>& api) {
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/main.html", mime_types::text_html));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/login", "static/login.html", mime_types::text_html));
class ServerResources {
std::unique_ptr<http::resource::StaticFileFactory> sf;
std::unique_ptr<api_driver::ApiDriver> api;
http::auth::AuthProvider auth{};
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/images/krokodil_vzryvaetsya_hd.gif", "static/krokodil.gif", mime_types::image_gif));
bool upgradeOrRebootRunning = false;
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/style.css", "static/style.css", mime_types::text_css));
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));
static void onUploadFirmware(const http::server::Request& req) {
std::ofstream f(FIRMWARE_LOCATION, std::ios::binary);
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
if (f.is_open()) {
f.write(req.payload.data(), static_cast<long>(req.payload.size()));
f.close();
} else {
throw std::runtime_error("File is not open");
}
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const char* json = R"({"key":"value"})";
rep.content.insert(rep.content.end(), json, json + strlen(json));
}));
void doTerminalUpgrade() const {
api->executeInApi([](api_driver::proxy::CpProxy& cp) {
cp.setDmaDebug("begin_save_config", "");
std::string cmd(UPGRADE_COMMAND);
cmd += " ";
cmd += FIRMWARE_LOCATION;
system(cmd.c_str());
cp.setDmaDebug("save_config", "");
});
}
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/mainStatistics", [api](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
}
#ifdef MODEM_IS_TDMA
void doTerminalUpgradeOta() const {
api->executeInApi([&](auto& cp) {
cp.setDmaDebug("begin_save_config", "");
std::string cmd(UPGRADE_COMMAND);
cmd += " ";
cmd += api->getOtaFileLocation();
system(cmd.c_str());
cp.setDmaDebug("save_config", "");
});
}
#endif
public:
#if defined(MODEM_IS_TDMA)
static constexpr const char* INDEX_HTML = "/main-tdma.html";
#elif defined(MODEM_IS_SCPC)
static constexpr const char* INDEX_HTML = "/main-scpc.html";
#elif defined(MODEM_IS_SHPS)
static constexpr const char* INDEX_HTML = "/main-shps.html";
#else
#error "Modem type not defined!"
#endif
static constexpr const char* LOGIN_HTML = "/login.html";
static constexpr const char* DEV_HTML = "/dev.html";
// картинки, их даже можно кешировать
static constexpr const char* FAVICON_ICO = "/favicon.ico";
static constexpr const char* VUE_JS = "/js/vue.js"; // это тоже можно кешировать
static constexpr const char* CHARTJS = "/js/chart.js";
static constexpr const char* MOMENT_JS = "/js/moment.js";
static constexpr const char* CHARTJS_ADAPTER_MOMENT = "/js/chartjs-adapter-moment.js";
// а эти стили нельзя кешировать в отладочной версии
static constexpr const char* STYLE_CSS = "/style.css";
static constexpr const char* FIELDS_CSS = "/fields.css";
static constexpr const char* INTERNET_JPG = "/internet.jpg";
ServerResources(const ServerResources&) = delete;
explicit ServerResources(const std::string& staticFilesPath): sf(std::make_unique<http::resource::StaticFileFactory>()), api(std::make_unique<api_driver::ApiDriver>()) {
api->startDaemon();
auth.users.emplace_back(std::make_shared<http::auth::User>("admin", "",
http::auth::User::WATCH_STATISTICS |
http::auth::User::RESET_PACKET_STATISTICS |
http::auth::User::WATCH_SETTINGS |
http::auth::User::EDIT_SETTINGS |
http::auth::User::UPDATE_FIRMWARE |
http::auth::User::SETUP_QOS));
// пароль fuckyou123
auth.users.emplace_back(std::make_shared<http::auth::User>("developer", "10628cfc434fb87f31d675d37e0402c2d824cfe8393aff7a61ee57aaa7d909c3", http::auth::User::SUPERUSER));
sf->registerFile(staticFilesPath + "/favicon.png", FAVICON_ICO, mime_types::image_png, true);
sf->registerFile(staticFilesPath + CHARTJS, CHARTJS, mime_types::javascript, true);
sf->registerFile(staticFilesPath + MOMENT_JS, MOMENT_JS, mime_types::javascript, true);
sf->registerFile(staticFilesPath + CHARTJS_ADAPTER_MOMENT, CHARTJS_ADAPTER_MOMENT, mime_types::javascript, true);
#ifdef USE_DEBUG
sf->registerFile(staticFilesPath + VUE_JS, VUE_JS, mime_types::javascript, true);
#else
sf->registerFile(staticFilesPath + "/js/vue.prod.js", VUE_JS, mime_types::javascript, true);
#endif
sf->registerFile(staticFilesPath + STYLE_CSS, STYLE_CSS, mime_types::text_css, true);
sf->registerFile(staticFilesPath + FIELDS_CSS, FIELDS_CSS, mime_types::text_css, true);
sf->registerFile(staticFilesPath + INDEX_HTML, INDEX_HTML, mime_types::text_html, false);
sf->registerFile(staticFilesPath + DEV_HTML, DEV_HTML, mime_types::text_html, false);
sf->registerFile(staticFilesPath + LOGIN_HTML, LOGIN_HTML, mime_types::text_html, true);
sf->registerFile(staticFilesPath + INTERNET_JPG, INTERNET_JPG, mime_types::image_jpeg, true);
}
void registerResources(http::server::Server& s) {
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/", [this](const auto& req, auto& rep) {
auto user = auth.getSession(req, rep);
if (user == nullptr) {
http::server::httpRedirect(rep, "/login");
} else {
sf->serve(INDEX_HTML, rep);
}
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/login", [this](const auto& req, auto& rep) {
if (req.method == "GET") {
auto user = auth.getSession(req, rep);
if (user == nullptr) {
sf->serve(LOGIN_HTML, rep);
} else {
http::server::httpRedirect(rep, "/");
}
} else if (req.method == "POST") {
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
auto u = auth.doAuth(reqJson["username"], reqJson["password"], req, rep);
if (u == nullptr) {
throw std::runtime_error("invalid session");
}
std::string result = R"({"redirect":"/"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
} catch (std::exception &e) {
BOOST_LOG_TRIVIAL(error) << e.what() << std::endl;
std::string result = R"({"error":"Неверный логин или пароль"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
} else {
http::server::stockReply(http::server::bad_request, rep);
}
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/logout", [](const auto& req, auto& rep) {
if (req.method == "GET") {
http::server::httpRedirect(rep, "/login");
rep.headers.push_back({.name = "Set-Cookie", .value = http::auth::jwt::EMPTY_AUTH_COOKIE});
} else {
http::server::stockReply(http::server::bad_request, rep);
}
}));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(FAVICON_ICO, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FAVICON_ICO, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(STYLE_CSS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(STYLE_CSS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(FIELDS_CSS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FIELDS_CSS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(VUE_JS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(VUE_JS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(CHARTJS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(CHARTJS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(MOMENT_JS, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(MOMENT_JS, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(CHARTJS_ADAPTER_MOMENT, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(CHARTJS_ADAPTER_MOMENT, rep); }));
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>(INTERNET_JPG, [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(INTERNET_JPG, rep); }));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/statistics", this->auth, http::auth::User::WATCH_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
resultJson["status"] = "ok";
resultJson["state"] = api->loadTerminalState();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/get/statistics): Can't get terminal state: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/settings", this->auth, http::auth::User::WATCH_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/get/settings): Can't get object: " << e.what();
resultJson.clear();
rep.status = http::server::internal_server_error;
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/get/aboutFirmware", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
resultJson["status"] = "ok";
resultJson["firmware"] = api->loadFirmwareVersion();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/get/aboutFirmware): Can't get object: " << e.what();
resultJson.clear();
rep.status = http::server::internal_server_error;
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetPacketStatistics", this->auth, http::auth::User::RESET_PACKET_STATISTICS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
api->resetPacketStatistics();
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok")";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/qos", this->auth, http::auth::User::SETUP_QOS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setQosSettings(reqJson);
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/qos): Can't set QoS settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/buclnb", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setBucLnbSettings(reqJson);
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/buclnb): Can't set BUC LNB settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#endif
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/dpdi", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setDpdiSettings(reqJson);
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/dpdi): Can't set DPDI settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#endif
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/rxtx", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setRxTxSettings(reqJson);
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/rxtx): Can't set RX/TX settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/network", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setNetworkSettings(reqJson);
resultJson["status"] = "ok";
resultJson["settings"] = api->loadSettings();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/network): Can't set network settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#ifdef MODEM_IS_TDMA
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/cesPassword", this->auth, http::auth::User::EDIT_SETTINGS, [this](const http::server::Request& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
auto password = reqJson["password"].get<std::string>();
this->api->executeInApi([&password](auto& cp) {
cp.setNetwork("ces_password", password);
});
resultJson["status"] = "ok";
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/cesPassword): Can't set CES password: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#endif
#endif
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/reboot", this->auth, 0, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
this->upgradeOrRebootRunning = true;
system(REBOOT_COMMAND);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/resetSettings", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const std::string result = R"({"status":"ok"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
api->resetDefaultSettings();
system(REBOOT_COMMAND);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/firmwareUpdate", this->auth, http::auth::User::UPDATE_FIRMWARE, [this](const auto& req, auto& rep) {
if (req.method != "PUT") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
this->upgradeOrRebootRunning = true;
onUploadFirmware(req);
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"status":"ok","fwsize":)";
result += std::to_string(req.payload.size());
result += R"(,"sha256":")";
result += http::utils::sha256(req.payload.data(), req.payload.size());
result += "\"}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
this->upgradeOrRebootRunning = false;
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/doFirmwareUpgrade", this->auth, http::auth::User::UPDATE_FIRMWARE, [this](const auto& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
this->upgradeOrRebootRunning = true;
#ifdef MODEM_IS_TDMA
if (req.url->params.find("ota") != req.url->params.end()) {
doTerminalUpgradeOta();
} else {
doTerminalUpgrade();
}
#else
doTerminalUpgrade();
#endif
resultJson["status"] = "ok";
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/doFirmwareUpgrade): Error: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev", this->auth, http::auth::User::DEVELOPER, [this](const auto& req, auto& rep) {
boost::ignore_unused(req);
sf->serve(DEV_HTML, rep);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev/cpapicall", this->auth, http::auth::User::DEVELOPER, [this](const http::server::Request& req, auto& rep) {
if (req.method != "POST") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
if (req.url->params.find("f") == req.url->params.end()) {
http::server::stockReply(http::server::bad_request, rep);
return;
}
const auto func = req.url->params["f"];
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
resultJson["status"] = "error";
if (func == "SetDmaDebug") {
if (req.url->params.find("param") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `param`";
} else if (req.url->params.find("value") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `value`";
} else {
this->api->executeInApi([&](auto& cp) {
cp.setDmaDebug(req.url->params["param"], req.url->params["value"]);
});
}
} else if (func == "GetDmaDebug") {
if (req.url->params.find("param") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `param`";
} else {
this->api->executeInApi([&](auto& cp) {
resultJson["status"] = "ok";
resultJson["result"] = cp.getDmaDebug(req.url->params["param"]);
});
}
} else if (func == "SetNetwork") {
if (req.url->params.find("param") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `param`";
} else if (req.url->params.find("value") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `value`";
} else {
this->api->executeInApi([&](auto& cp) {
cp.setNetwork(req.url->params["param"], req.url->params["value"]);
});
}
} else if (func == "GetNetwork") {
if (req.url->params.find("param") == req.url->params.end()) {
rep.status = http::server::bad_request;
resultJson["error"] = "missing required adgument: `param`";
} else {
this->api->executeInApi([&](auto& cp) {
resultJson["status"] = "ok";
resultJson["result"] = cp.getNetwork(req.url->params["param"]);
});
}
} else {
resultJson["error"] = "function not supported";
}
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/qos): Can't set QoS settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev/settings", this->auth, http::auth::User::DEVELOPER, [this](const auto& req, auto& rep) {
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::text_plain)});
nlohmann::json resultJson;
try {
if (req.method == "GET") {
resultJson["status"] = "ok";
resultJson["logstat"] = api->getLoggingStatisticsSettings();
} else if (req.method == "POST") {
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
api->setLoggingStatisticsSettings(reqJson);
resultJson["status"] = "ok";
resultJson["logstat"] = api->getLoggingStatisticsSettings();
} else {
rep.status = http::server::bad_request;
resultJson["status"] = "error";
resultJson["error"] = "unsupported request type";
}
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/qos): Can't set QoS settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
auto result = resultJson.dump();
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
#endif
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/dev/logs.csv", this->auth, http::auth::User::DEVELOPER, [this](const auto& req, auto& rep) {
if (req.method != "GET") {
http::server::stockReply(http::server::bad_request, rep);
return;
}
rep.status = http::server::ok;
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::text_plain)});
rep.content.clear();
http::resource::loadFile("/tmp/weblog-statistics.csv", rep.content);
}));
#endif
}
~ServerResources() = default;
};
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"mainState":)";
result += api->loadTerminalState();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
}
int main(int argc, char *argv[]) {
try {
@@ -116,9 +682,29 @@ int main(int argc, char *argv[]) {
if (argc != 4 && argc != 5) {
std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver nossl 0.0.0.0 80\n";
std::cerr << " receiver nossl 0.0.0.0 80 .\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver nossl 0::0 80\n";
std::cerr << " receiver nossl 0::0 80 .\n";
return 1;
}
if (strcmp(argv[1], "nossl") != 0 && strcmp(argv[1], "ssl") != 0) {
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
return 1;
}
int serverPort;
try {
size_t idx = 0;
serverPort = std::stoi(std::string(argv[3]), &idx);
if (serverPort < 0 || serverPort > 0xffff) {
throw std::invalid_argument("Out of range");
}
if (idx != strlen(argv[3])) {
throw std::invalid_argument("Invalid number");
}
} catch (std::exception& e) {
std::cerr << "Wrong server port `" << argv[3] << "`: " << e.what() << std::endl;
return 1;
}
@@ -128,23 +714,36 @@ int main(int argc, char *argv[]) {
#ifdef USE_DEBUG
BOOST_LOG_TRIVIAL(info) << "Starting DEBUG " << argv[0];
#else
BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0];
BOOST_LOG_TRIVIAL(info) << "Starting RELEASE " << argv[0];
#endif
auto api = std::make_shared<api_driver::ApiDriver>();
BOOST_LOG_TRIVIAL(info) << ("Build time: " PROJECT_BUILD_TIME);
BOOST_LOG_TRIVIAL(info) << ("Git version: " PROJECT_GIT_REVISION);
#ifdef USE_DEBUG
http::auth::jwt::secretKey = "^}u'ZKyQ%;+:lnh^GS7!=G~nRK?7[{``";
BOOST_LOG_TRIVIAL(info) << "DEBUG build use pre-created key " << http::auth::jwt::secretKey;
#else
http::auth::jwt::generateSecretKey();
BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey;
#endif
const std::string staticFilesPath = (argc == 5 ? argv[4]: ".");
BOOST_LOG_TRIVIAL(info) << "Use static files path: " << staticFilesPath << "/";
ServerResources resources(staticFilesPath);
// Initialise the server.
std::unique_ptr<http::server::Server> s;
if (strcmp(argv[1], "nossl") == 0) {
s = std::make_unique<http::server::Server>(argv[2], argv[3]);
initResources(*s, api);
BOOST_LOG_TRIVIAL(info) << "Run server on " << argv[2] << ":" << serverPort;
s = std::make_unique<http::server::Server>(argv[2], serverPort);
resources.registerResources(*s);
s->run();
} else if (strcmp(argv[1], "ssl") == 0) {
const auto cert = loadFile("cert.pem");
const auto key = loadFile("key.pem");
const auto dh = loadFile("dh.pem");
} else {
std::vector<char> cert; http::resource::loadFile("cert.pem", cert);
std::vector<char> key; http::resource::loadFile("key.pem", key);
std::vector<char> dh; http::resource::loadFile("dh.pem", dh);
auto ctx = std::make_shared<ssl::context>(ssl::context::tlsv12);
@@ -158,12 +757,10 @@ int main(int argc, char *argv[]) {
ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem);
ctx->use_tmp_dh(boost::asio::buffer(dh));
s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
initResources(*s, api);
BOOST_LOG_TRIVIAL(info) << "Run server on " << argv[2] << ":" << serverPort;
s = std::make_unique<http::server::Server>(argv[2], serverPort, ctx);
resources.registerResources(*s);
s->run();
} else {
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
return 1;
}
} catch (std::exception &e) {
BOOST_LOG_TRIVIAL(error) << e.what() << std::endl;

View File

@@ -3,16 +3,18 @@
#include <boost/log/trivial.hpp>
#include <boost/beast.hpp>
#include "server.hpp"
#include "version.h"
namespace http::server {
const char* SERVER_HEADER_VALUE = "TerminalWebServer v0.1";
const char* SERVER_HEADER_VALUE = "TerminalWebServer " PROJECT_GIT_REVISION;
Connection::Connection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler)
: socket_(std::move(socket)), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
: socket_(std::move(socket)), connection_manager_(manager), request_handler_(std::move(handler)), request_(false), reply_() {
}
void Connection::start() {
request_parser_.reset();
request_.reset();
doRead();
}
@@ -23,14 +25,6 @@ namespace http::server {
Connection::~Connection() = default;
void Connection::doRead() {
request_parser_.reset();
request_.headers.clear();
request_.method.clear();
request_.queryUri.clear();
if (request_.url != nullptr) {
request_.url.reset(nullptr);
}
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
@@ -43,6 +37,7 @@ namespace http::server {
doWrite();
} else if (result == RequestParser::bad) {
stockReply(bad_request, reply_);
needClose = true;
doWrite();
} else {
doRead();
@@ -55,19 +50,20 @@ namespace http::server {
void Connection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
if (!reply_.content.empty()) {
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
}
if (request_.http_version_major == 1) {
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.httpVersionMajor == 1) {
reply_.headers.push_back({.name = "Connection", .value = "keep-alive"});
}
BOOST_LOG_TRIVIAL(info) << "HTTP query " << reply_.status << " " << request_.method << " " << request_.queryUri;
const auto ep = socket_.remote_endpoint();
BOOST_LOG_TRIVIAL(info) << "HTTP query " << ep.address().to_string() << ":" << ep.port() << " " << reply_.status << " " << request_.method << " " << request_.queryUri;
auto self(shared_from_this());
async_write(socket_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) {
if (!ec && !needClose) {
// keep alive Connection
request_parser_.reset();
request_.reset();
doRead();
} else {
connection_manager_.stop(shared_from_this());
@@ -76,11 +72,13 @@ namespace http::server {
}
SslConnection::SslConnection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler, const std::shared_ptr<boost::asio::ssl::context>& ctx):
stream_(std::move(socket), *ctx), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
stream_(std::move(socket), *ctx), connection_manager_(manager), request_handler_(std::move(handler)), request_(true), reply_() {
}
void SslConnection::start() {
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
request_parser_.reset();
request_.reset();
// Perform the SSL handshake
stream_.async_handshake(boost::asio::ssl::stream_base::server, boost::beast::bind_front_handler([this](auto ec) {
@@ -90,6 +88,12 @@ namespace http::server {
}
void SslConnection::stop() {
try {
stream_.next_layer().socket().close();
stream_.shutdown();
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "SslConnection::stop(): Can't shutdown ssl socket: " << e.what();
}
}
SslConnection::~SslConnection() = default;
@@ -97,11 +101,6 @@ namespace http::server {
void SslConnection::doRead() {
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
request_parser_.reset();
request_.headers.clear();
request_.method.clear();
request_.queryUri.clear();
auto self(shared_from_this());
stream_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
@@ -114,6 +113,7 @@ namespace http::server {
doWrite();
} else if (result == RequestParser::bad) {
stockReply(bad_request, reply_);
needClose = true;
doWrite();
} else {
doRead();
@@ -126,19 +126,20 @@ namespace http::server {
void SslConnection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
if (!reply_.content.empty()) {
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
}
if (request_.http_version_major == 1) {
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.httpVersionMajor == 1) {
reply_.headers.push_back({.name = "Connection", .value = "keep-alive"});
}
BOOST_LOG_TRIVIAL(info) << "HTTPS query " << reply_.status << " " << request_.method << " " << request_.queryUri;
const auto ep = stream_.next_layer().socket().remote_endpoint();
BOOST_LOG_TRIVIAL(info) << "HTTPS query " << ep.address().to_string() << ":" << ep.port() << " " << reply_.status << " " << request_.method << " " << request_.queryUri;
auto self(shared_from_this());
async_write(stream_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) {
if (!ec && !needClose) {
// keep alive Connection
request_parser_.reset();
request_.reset();
doRead();
} else {
connection_manager_.stop(shared_from_this());

View File

@@ -13,7 +13,7 @@
namespace http::server {
using request_handler = std::function<void(const Request &req, reply &rep)>;
using request_handler = std::function<void(const Request &req, Reply &rep)>;
class ConnectionManager;
/// Represents a single Connection from a client. Base class
@@ -71,7 +71,9 @@ namespace http::server {
RequestParser request_parser_;
/// The reply to be sent back to the client.
reply reply_;
Reply reply_;
bool needClose = false;
};
class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> {
@@ -113,7 +115,9 @@ namespace http::server {
RequestParser request_parser_;
/// The reply to be sent back to the client.
reply reply_;
Reply reply_;
bool needClose = false;
};
typedef std::shared_ptr<ConnectionBase> connection_ptr;

View File

@@ -36,6 +36,7 @@ std::string http::server::mime_types::toString(Mime m) {
case text_plain: return "text/plain";
case text_html: return "text/html";
case text_css: return "text/css";
case video_mp4: return "video/mp4";
case json: return "application/json";
case javascript: return "application/javascript";
case blob:

View File

@@ -15,6 +15,7 @@ namespace http::server::mime_types {
text_plain, // text/plain
text_html, // text/html
text_css, // text/css
video_mp4, // video/mp4
json, // application/json
javascript, // application/javascript
blob // application/octet-stream

View File

@@ -13,6 +13,7 @@ namespace http::server {
const std::string multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n";
const std::string moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n";
const std::string moved_temporarily = "HTTP/1.1 302 Moved Temporarily\r\n";
const std::string see_other_redirect = "HTTP/1.1 303 See Other\r\n";
const std::string not_modified = "HTTP/1.1 304 Not Modified\r\n";
const std::string bad_request = "HTTP/1.1 400 Bad Request\r\n";
const std::string unauthorized = "HTTP/1.1 401 Unauthorized\r\n";
@@ -39,6 +40,8 @@ namespace http::server {
return boost::asio::buffer(moved_permanently);
case status_type::moved_temporarily:
return boost::asio::buffer(moved_temporarily);
case status_type::see_other_redirect:
return boost::asio::buffer(see_other_redirect);
case status_type::not_modified:
return boost::asio::buffer(not_modified);
case status_type::bad_request:
@@ -68,7 +71,7 @@ namespace http::server {
const char crlf[] = {'\r', '\n'};
} // namespace misc_strings
std::vector<boost::asio::const_buffer> reply::to_buffers() const {
std::vector<boost::asio::const_buffer> Reply::to_buffers() const {
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(status_strings::to_buffer(status));
for (const auto & h : headers) {
@@ -78,84 +81,86 @@ namespace http::server {
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
}
buffers.emplace_back(boost::asio::buffer(misc_strings::crlf));
buffers.emplace_back(boost::asio::buffer(content));
if (!content.empty()) {
buffers.emplace_back(boost::asio::buffer(content));
}
return buffers;
}
namespace stock_replies {
constexpr char ok[] = "";
constexpr char created[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Created</title></head>"
"<body><h1>201 Created</h1></body>"
"</html>";
constexpr char accepted[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Accepted</title></head>"
"<body><h1>202 Accepted</h1></body>"
"</html>";
constexpr char no_content[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>No Content</title></head>"
"<body><h1>204 Content</h1></body>"
"</html>";
constexpr char multiple_choices[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Multiple Choices</title></head>"
"<body><h1>300 Multiple Choices</h1></body>"
"</html>";
constexpr char moved_permanently[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Moved Permanently</title></head>"
"<body><h1>301 Moved Permanently</h1></body>"
"</html>";
constexpr char moved_temporarily[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Moved Temporarily</title></head>"
"<body><h1>302 Moved Temporarily</h1></body>"
"</html>";
constexpr char not_modified[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Not Modified</title></head>"
"<body><h1>304 Not Modified</h1></body>"
"</html>";
constexpr char bad_request[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Bad Request</title></head>"
"<body><h1>400 Bad Request</h1></body>"
"</html>";
constexpr char unauthorized[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Unauthorized</title></head>"
"<body><h1>401 Unauthorized</h1></body>"
"</html>";
constexpr char forbidden[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Forbidden</title></head>"
"<body><h1>403 Forbidden</h1></body>"
"</html>";
constexpr char not_found[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Not Found</title></head>"
"<body><h1>404 Not Found</h1></body>"
"</html>";
constexpr char internal_server_error[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Internal Server Error</title></head>"
"<body><h1>500 Internal Server Error</h1></body>"
"</html>";
constexpr char not_implemented[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Not Implemented</title></head>"
"<body><h1>501 Not Implemented</h1></body>"
"</html>";
constexpr char bad_gateway[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Bad Gateway</title></head>"
"<body><h1>502 Bad Gateway</h1></body>"
"</html>";
constexpr char service_unavailable[] =
"<html>"
"<!DOCTYPE html>\n<html>"
"<head><title>Service Unavailable</title></head>"
"<body><h1>503 Service Unavailable</h1></body>"
"</html>";
@@ -206,10 +211,16 @@ namespace http::server {
}
} // namespace stock_replies
void stockReply(status_type status, reply& rep) {
void stockReply(status_type status, Reply& rep) {
rep.status = status;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::text_html)});
stock_replies::as_content(status, rep.content);
}
void httpRedirect(Reply &rep, const std::string &location) {
rep.status = see_other_redirect;
rep.content.clear();
rep.headers.push_back({.name = "Location", .value = location});
}
} // namespace http::server

View File

@@ -16,6 +16,7 @@ namespace http::server {
multiple_choices = 300,
moved_permanently = 301,
moved_temporarily = 302,
see_other_redirect = 303,
not_modified = 304,
bad_request = 400,
unauthorized = 401,
@@ -28,7 +29,7 @@ namespace http::server {
};
/// A reply to be sent to a client.
struct reply {
struct Reply {
status_type status;
/// The headers to be included in the reply.
@@ -44,8 +45,9 @@ namespace http::server {
};
/// Get a stock reply.
void stockReply(status_type status, reply& rep);
void stockReply(status_type status, Reply& rep);
void httpRedirect(Reply& rep, const std::string& location);
} // namespace http::Server

View File

@@ -19,14 +19,24 @@ namespace http::server {
};
/// A request received from a client.
struct Request {
class Request {
public:
Request(bool secure);
void reset();
std::string getHeaderValue(const std::string& headerName) const;
std::string method;
std::string queryUri;
std::unique_ptr<Url> url;
bool is_keep_alive;
int http_version_major;
int http_version_minor;
bool isKeepAlive{};
const bool isSecure;
int httpVersionMajor{};
int httpVersionMinor{};
std::vector<header> headers;
std::vector<char> payload;
~Request();
};
} // namespace http::Server

View File

@@ -1,11 +1,30 @@
#include "request_parser.hpp"
#include <sstream>
#include "request.hpp"
namespace http::server {
constexpr int HTTP_MAX_HEADERS = 64;
/**
* Функция, позволяющая или запрещающая выделение размера тела для запросов.
* @return true, если тело удовлетворяет размерам
*/
static bool requestBodySizeResolver(Request& req, size_t reqSize) {
// разрешаем тело только для POST запросов
if (req.method == "POST") {
return reqSize < 0x4000; // 16кб на все POST-запросы к API будет более чем достаточно
}
// это для обновления прошивки
if (req.method == "PUT" && req.url->path == "/api/firmwareUpdate") {
return reqSize <= HTTP_MAX_PAYLOAD;
}
return false;
}
static void parseParams(Url& u, const std::string& query) {
std::istringstream iss(query);
std::string param;
@@ -32,6 +51,32 @@ namespace http::server {
Url::~Url() = default;
Request::Request(bool secure): isSecure(secure) {}
void Request::reset() {
method = "";
queryUri = "";
if (url != nullptr) {
url.reset(nullptr);
}
isKeepAlive = false;
httpVersionMajor = 0;
httpVersionMinor = 0;
headers.clear();
payload.clear();
}
std::string Request::getHeaderValue(const std::string &headerName) const {
for (const auto& header: headers) {
if (boost::iequals(header.name, headerName)) {
return header.value;
}
}
return "";
}
Request::~Request() = default;
RequestParser::RequestParser()
: state_(method_start) {
@@ -39,10 +84,17 @@ namespace http::server {
void RequestParser::reset() {
state_ = method_start;
contentLenghtHeader = 0;
}
RequestParser::result_type RequestParser::consume(Request &req, char input) {
switch (state_) {
case expecting_payload:
req.payload.push_back(input);
if (req.payload.size() < contentLenghtHeader) {
return indeterminate;
}
return good;
case method_start:
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
@@ -55,36 +107,34 @@ namespace http::server {
if (input == ' ') {
state_ = uri;
return indeterminate;
} else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
} else {
req.method.push_back(input);
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
req.method.push_back(input);
return indeterminate;
case uri:
if (input == ' ') {
state_ = http_version_h;
return indeterminate;
} else if (is_ctl(input)) {
return bad;
} else {
req.queryUri.push_back(input);
return indeterminate;
}
if (is_ctl(input)) {
return bad;
}
req.queryUri.push_back(input);
return indeterminate;
case http_version_h:
if (input == 'H') {
state_ = http_version_t_1;
return indeterminate;
} else {
return bad;
}
return bad;
case http_version_t_1:
if (input == 'T') {
state_ = http_version_t_2;
return indeterminate;
} else {
return bad;
}
return bad;
case http_version_t_2:
if (input == 'T') {
state_ = http_version_p;
@@ -101,8 +151,8 @@ namespace http::server {
}
case http_version_slash:
if (input == '/') {
req.http_version_major = 0;
req.http_version_minor = 0;
req.httpVersionMajor = 0;
req.httpVersionMinor = 0;
state_ = http_version_major_start;
return indeterminate;
} else {
@@ -110,7 +160,7 @@ namespace http::server {
}
case http_version_major_start:
if (is_digit(input)) {
req.http_version_major = req.http_version_major * 10 + input - '0';
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
state_ = http_version_major;
return indeterminate;
} else {
@@ -121,14 +171,14 @@ namespace http::server {
state_ = http_version_minor_start;
return indeterminate;
} else if (is_digit(input)) {
req.http_version_major = req.http_version_major * 10 + input - '0';
req.httpVersionMajor = req.httpVersionMajor * 10 + input - '0';
return indeterminate;
} else {
return bad;
}
case http_version_minor_start:
if (is_digit(input)) {
req.http_version_minor = req.http_version_minor * 10 + input - '0';
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
state_ = http_version_minor;
return indeterminate;
} else {
@@ -139,7 +189,7 @@ namespace http::server {
state_ = expecting_newline_1;
return indeterminate;
} else if (is_digit(input)) {
req.http_version_minor = req.http_version_minor * 10 + input - '0';
req.httpVersionMinor = req.httpVersionMinor * 10 + input - '0';
return indeterminate;
} else {
return bad;
@@ -155,17 +205,23 @@ namespace http::server {
if (input == '\r') {
state_ = expecting_newline_3;
return indeterminate;
} else if (!req.headers.empty() && (input == ' ' || input == '\t')) {
}
if (!req.headers.empty() && (input == ' ' || input == '\t')) {
state_ = header_lws;
return indeterminate;
} else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
} else {
req.headers.emplace_back();
req.headers.back().name.push_back(input);
state_ = header_name;
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
if (req.headers.size() > HTTP_MAX_HEADERS) {
return bad;
}
req.headers.emplace_back();
req.headers.back().name.push_back(input);
state_ = header_name;
return indeterminate;
case header_lws:
if (input == '\r') {
state_ = expecting_newline_2;
@@ -183,12 +239,12 @@ namespace http::server {
if (input == ':') {
state_ = space_before_header_value;
return indeterminate;
} else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
} else {
req.headers.back().name.push_back(input);
return indeterminate;
}
if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
return bad;
}
req.headers.back().name.push_back(input);
return indeterminate;
case space_before_header_value:
if (input == ' ') {
state_ = header_value;
@@ -216,9 +272,21 @@ namespace http::server {
case expecting_newline_3:
if (input == '\n') {
req.url = std::make_unique<Url>(req.queryUri);
return good;
auto content_len = req.getHeaderValue("content-length");
if (content_len.empty()) {
return good;
}
contentLenghtHeader = std::stoul(content_len);
if (contentLenghtHeader == 0) {
return good;
}
if (requestBodySizeResolver(req, contentLenghtHeader)) {
state_ = expecting_payload;
return indeterminate;
}
}
return bad;
default:
return bad;
}

View File

@@ -8,7 +8,7 @@
namespace http::server {
struct Request;
class Request;
/// Parser for incoming requests.
class RequestParser {
@@ -73,8 +73,11 @@ namespace http::server {
space_before_header_value,
header_value,
expecting_newline_2,
expecting_newline_3
expecting_newline_3,
expecting_payload
} state_;
size_t contentLenghtHeader = 0;
};
} // namespace http::Server

View File

@@ -1,11 +1,12 @@
#include "resource.h"
#include <boost/log/trivial.hpp>
#include <fstream>
#include <utility>
static void loadFile(const std::string& path, std::vector<char>& content) {
void http::resource::loadFile(const std::string& path, std::vector<char>& content) {
std::ifstream is(path, std::ios::in | std::ios::binary);
if (!is) {
throw std::runtime_error("File not found");
throw std::runtime_error("File not found " + path);
}
content.clear();
@@ -19,44 +20,65 @@ static void loadFile(const std::string& path, std::vector<char>& content) {
}
}
http::resource::StaticFileResource::StaticFileResource(const std::string &path, const std::string &filePath, server::mime_types::Mime type): type(type) {
this->path = path;
#ifdef USE_DEBUG
BOOST_LOG_TRIVIAL(info) << "Skip loading file " << filePath << " (http path: " << path << ")";
this->filePath = filePath;
#else
BOOST_LOG_TRIVIAL(info) << "Load file " << filePath << " (http path: " << path << ")";
loadFile(filePath, this->content);
#endif
}
http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {}
void http::resource::StaticFileResource::handle(const server::Request &req, server::reply &rep) {
if (req.method != "GET") {
stockReply(server::bad_request, rep);
return;
http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(const std::string& path, std::string webPath, server::mime_types::Mime type, bool allowCache):
webPath(std::move(webPath)),
#ifdef USE_DEBUG
fsPath(path),
#endif
type(type), allowCache(allowCache) {
#ifdef USE_DEBUG
if (allowCache) {
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->webPath;
loadFile(path, this->content);
} else {
BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->webPath;
}
// TODO сделать поддержку range
#ifdef USE_DEBUG
BOOST_LOG_TRIVIAL(debug) << "Reload file " << filePath << " (http path: " << path << ")";
loadFile(this->filePath, rep.content);
#else
rep.content.clear();
rep.content.insert(rep.content.end(), this->content.begin(), this->content.end());
BOOST_LOG_TRIVIAL(info) << "Load static file " << path;
loadFile(path, this->content);
#endif
rep.status = server::ok;
rep.headers.clear();
// TODO сделать cache control
rep.headers.push_back({.name = "Content-Type", .value = toString(this->type)});
}
http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default;
http::resource::StaticFileFactory::StaticFileFactory() = default;
void http::resource::StaticFileFactory::registerFile(const std::string &path, const std::string &webPath, server::mime_types::Mime type, bool allowCache) {
this->files.emplace_back(path, webPath, type, allowCache);
}
http::resource::StaticFileResource::~StaticFileResource() = default;
http::resource::GenericResource::GenericResource(const std::string &path, const respGenerator &generator): generator_(generator) {
this->path = path;
void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) {
for (auto& f: this->files) {
if (f.webPath == path) {
#ifdef USE_DEBUG
if (f.allowCache) {
rep.content.clear();
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
} else {
BOOST_LOG_TRIVIAL(debug) << "Reload file " << path << " (http path: " << path << ")";
loadFile(f.fsPath, rep.content);
}
#else
rep.content.clear();
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
#endif
rep.status = server::ok;
// rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = server::mime_types::toString(f.type)});
if (f.allowCache) {
rep.headers.push_back({.name = "Cache-Control", .value = "max-age=86400"}); // сутки, думаю хватит
}
return;
}
}
}
void http::resource::GenericResource::handle(const server::Request &req, server::reply &rep) {
http::resource::StaticFileFactory::~StaticFileFactory() = default;
http::resource::GenericResource::GenericResource(const std::string &path, respGenerator generator): BasicResource(path), generator_(std::move(generator)) {}
void http::resource::GenericResource::handle(const server::Request &req, server::Reply &rep) {
this->generator_(req, rep);
}

View File

@@ -12,34 +12,41 @@ namespace http::resource {
*/
class BasicResource {
public:
BasicResource(std::string path);
std::string path;
virtual void handle(const server::Request &req, server::reply &rep) = 0;
virtual void handle(const server::Request &req, server::Reply &rep) = 0;
virtual ~BasicResource() = default;
};
/**
* Класс ресурса статического файла
*/
class StaticFileResource final : public BasicResource {
private:
server::mime_types::Mime type;
class StaticFileFactory {
class StaticFileDef {
public:
StaticFileDef(const std::string& path, std::string webPath, server::mime_types::Mime type, bool allowCache = true);
std::string webPath;
#ifdef USE_DEBUG
std::string filePath;
#else
std::vector<char> content;
std::string fsPath;
#endif
server::mime_types::Mime type;
bool allowCache;
std::vector<char> content;
~StaticFileDef();
};
std::vector<StaticFileDef> files;
public:
StaticFileResource(const std::string& path, const std::string& filePath, server::mime_types::Mime type);
StaticFileFactory();
void handle(const server::Request &req, server::reply &rep) override;
void registerFile(const std::string& path, const std::string &webPath, server::mime_types::Mime type, bool allowCache = true);
~StaticFileResource() override;
void serve(const std::string& path, server::Reply& rep);
~StaticFileFactory();
};
using respGenerator = std::function<void(const server::Request &req, server::reply &rep)>;
using respGenerator = std::function<void(const server::Request &req, server::Reply &rep)>;
/**
* Класс ресурса для POST-запросов
@@ -49,12 +56,14 @@ namespace http::resource {
respGenerator generator_;
public:
GenericResource(const std::string& path, const respGenerator& generator);
GenericResource(const std::string& path, respGenerator generator);
void handle(const server::Request &req, server::reply &rep) override;
void handle(const server::Request &req, server::Reply &rep) override;
~GenericResource() override;
};
void loadFile(const std::string& path, std::vector<char>& content);
}
#endif //RESOURCE_H

View File

@@ -1,6 +1,7 @@
#include "server.hpp"
#include <utility>
#include <boost/beast/core/basic_stream.hpp>
#include <boost/log/trivial.hpp>
namespace http::server {
@@ -22,7 +23,7 @@ namespace http::server {
connections_.clear();
}
Server::Server(const std::string &address, const std::string &port)
Server::Server(const std::string &address, int port)
: io_context_(1), signals_(io_context_), acceptor_(io_context_) {
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
@@ -38,7 +39,7 @@ namespace http::server {
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_context_);
boost::asio::ip::tcp::endpoint endpoint =
*resolver.resolve(address, port).begin();
*resolver.resolve(address, std::to_string(port)).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
@@ -47,7 +48,7 @@ namespace http::server {
doAccept();
}
Server::Server(const std::string &address, const std::string &port, std::shared_ptr<boost::asio::ssl::context> ctx):
Server::Server(const std::string &address, int port, std::shared_ptr<boost::asio::ssl::context> ctx):
ssl_ctx(std::move(ctx)), io_context_(1), signals_(io_context_), acceptor_(io_context_) {
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
@@ -62,7 +63,7 @@ namespace http::server {
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_context_);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, std::to_string(port)).begin();
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
@@ -111,7 +112,7 @@ namespace http::server {
});
}
void Server::requestHandler(const Request &req, reply &rep) {
void Server::requestHandler(const Request &req, Reply &rep) {
// Request path must be absolute and not contain "..".
if (req.url->path.empty() || req.url->path[0] != '/' || req.url->path.find("..") != std::string::npos) {
stockReply(bad_request, rep);
@@ -126,7 +127,12 @@ namespace http::server {
if (res->path != req.url->path) {
continue;
}
res->handle(req, rep);
try {
res->handle(req, rep);
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Server::requestHandler(): what = " << e.what();
stockReply(internal_server_error, rep);
}
return;
}

View File

@@ -44,8 +44,8 @@ namespace http::server {
Server &operator=(const Server &) = delete;
/// Construct the server to listen on the specified TCP address and port
explicit Server(const std::string &address, const std::string &port);
explicit Server(const std::string &address, const std::string &port, std::shared_ptr<boost::asio::ssl::context> ctx);
explicit Server(const std::string &address, int port);
explicit Server(const std::string &address, int port, std::shared_ptr<boost::asio::ssl::context> ctx);
std::vector<std::unique_ptr<resource::BasicResource>> resources;
@@ -74,7 +74,7 @@ namespace http::server {
ConnectionManager connection_manager_;
/// Handle a request and produce a reply.
void requestHandler(const Request &req, reply &rep);
void requestHandler(const Request &req, Reply &rep);
};
} // namespace http::Server

View File

@@ -1,259 +1,198 @@
#include "terminal_api_driver.h"
#include "terminal_api/ControlProtoCInterface.h"
#include <sstream>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <shared_mutex>
#include <boost/thread.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <sys/sysinfo.h>
#include "common/nlohmann/json.hpp"
#include "api-driver/daemon.h"
/*
timer_->timeout().connect([=]{
double txpwd = 0;
CP_GetGain(sid, "TXPWD", &txpwd);
transmitt_active->set_end(!txpwd);
CP_GetLevelDemod(sid, "modcod_tx", &txpwd);
modcod_tx->setText(std::to_string(static_cast<uint32_t>(txpwd) >> 2));
uint8_t type_pack = static_cast<uint8_t>(txpwd);
type_pack = type_pack & 0b00000010;
if(type_pack)
type_pack_lbl->setText("short");
else
type_pack_lbl->setText("normal");
typedef boost::property_tree::ptree::path_type json_path;
uint8_t is_pilots = static_cast<uint8_t>(txpwd);
is_pilots = is_pilots & 0b00000001;
if(is_pilots)
pilot_lbl->setText("pilots");
else
pilot_lbl->setText("no pilots");
CP_GetLevelDemod(sid, "snr_acm", &txpwd);
std::stringstream buf_double;
buf_double << std::fixed <<
std::setprecision(2) << txpwd;
snr_acm->setText(buf_double.str());
});
api_driver::ApiDriver::ApiDriver() = default;
*/
api_driver::ApiDriver::ApiDriver() {
CP_Login("admin", "pass", &sid, &access);
void api_driver::ApiDriver::startDaemon() {
if (daemon == nullptr) {
daemon = std::make_unique<TerminalApiDaemon>();
BOOST_LOG_TRIVIAL(info) << "api_driver::ApiDriver::startDaemon(): API daemon succes started!";
}
}
static bool DriverCP_GetLevelDemod(TSID sid, const char* param) {
double variable_dbl = 0;
CP_GetLevelDemod(sid, param, &variable_dbl);
return variable_dbl == 0;
std::string api_driver::buildEscapedString(const std::string& source) {
std::string str(source);
size_t start_pos = 0;
while((start_pos = str.find('\"', start_pos)) != std::string::npos) {
str.replace(start_pos, 1, "\\\"");
start_pos += 2;
}
for (start_pos = 0; start_pos < str.size() && (str[start_pos] == ' ' || str[start_pos] == '\n' || str[start_pos] == '\t'); start_pos++) {}
size_t end_pos = str.size() - 1;
for (; end_pos > start_pos && end_pos != 0 && (str[end_pos] == ' ' || str[end_pos] == '\n' || str[end_pos] == '\t'); end_pos--) {}
return "\"" + str.substr(start_pos, end_pos - start_pos + 1) + "\"";
}
static bool DriverCP_GetTxPower(TSID sid) {
double variable_dbl = 0;
CP_GetGain(sid, "TXPWD", &variable_dbl);
return variable_dbl != 0;
std::tuple<uint8_t, uint8_t> translateCoordinates(double abs) {
auto deg = static_cast<uint8_t>(abs);
double min_double = (abs - deg) * 60;
auto min = static_cast<uint8_t>(min_double);
return std::make_tuple(deg, min);
}
static bool DriverCp_GetIsCinC(TSID sid) {
uint32_t mode_demod = 0;
CP_GetDemodulatorParams(sid, "mode_demod", &mode_demod);
return mode_demod == 1;
}
static const char* boolAsStr(bool value) {
return value ? "true" : "false";
}
std::string api_driver::ApiDriver::loadTerminalState() {
std::stringstream result;
result << "{";
const auto txEnable = DriverCP_GetTxPower(sid);
const auto isCinC = DriverCp_GetIsCinC(sid);
result << "\"isCinC\":" << boolAsStr(isCinC);
// формируем структуру для TX
{
result << ",\"tx.state\":" << boolAsStr(txEnable);
double tmp = 0;
char tmpStr[32];
CP_GetLevelDemod(sid, "snr_acm", &tmp);
sprintf(tmpStr, "%.2f", tmp);
result << ",\"tx.snr\":" << tmpStr;
CP_GetLevelDemod(sid, "modcod_tx", &tmp);
result << ",\"tx.modcod\":" << (static_cast<uint32_t>(tmp) >> 2);
if (static_cast<uint8_t>(tmp) & 0b00000010) {
result << R"(,"tx.frameSize":"short")";
} else {
result << R"(,"tx.frameSize":"normal")";
}
if (static_cast<uint8_t>(tmp) & 0b00000001) {
result << R"(,"tx.pilots":"pilots")";
} else {
result << R"(,"tx.pilots":"no pilots")";
}
std::string speed;
CP_GetDmaDebug(sid, "speed_tx", &speed);
sprintf(tmpStr, "%.3f", std::atof(speed.c_str()) / 128.0);
result << ",\"tx.speedOnTxKbit\":" << tmpStr;
CP_GetDmaDebug(sid, "speed_tx_iface", &speed);
sprintf(tmpStr, "%.3f", std::atof(speed.c_str()) / 128.0);
result << ",\"tx.speedOnIifKbit\":" << tmpStr;
nlohmann::json api_driver::ApiDriver::loadTerminalState() const {
if (daemon == nullptr) {
return R"({"error": "api daemon not started!"})";
}
// формируем структуру для RX
{
const auto sym_sync_lock = DriverCP_GetLevelDemod(sid, "sym_sync_lock"); // захват символьной
const auto freq_search_lock = DriverCP_GetLevelDemod(sid, "freq_lock"); // Захват поиска по частоте
const auto afc_lock = DriverCP_GetLevelDemod(sid, "afc_lock"); // захват ФАПЧ
const auto pkt_sync = DriverCP_GetLevelDemod(sid, "pkt_sync"); // захват пакетной синхронизации
const auto receive_active = sym_sync_lock && freq_search_lock && afc_lock && pkt_sync;
obj::TerminalState state;
daemon->getState(state);
obj::TerminalDeviceState devState;
daemon->getDeviceState(devState);
result << ",\"rx.state\":" << boolAsStr(receive_active);
result << ",\"rx.sym_sync_lock\":" << boolAsStr(sym_sync_lock);
result << ",\"rx.freq_search_lock\":" << boolAsStr(freq_search_lock);
result << ",\"rx.afc_lock\":" << boolAsStr(afc_lock);
result << ",\"rx.pkt_sync\":" << boolAsStr(pkt_sync);
double tmpd = 0; uint32_t tmpu32 = 0;
char tmpStr[32];
CP_GetLevelDemod(sid, "snr", &tmpd);
sprintf(tmpStr, "%.2f", tmpd);
result << ",\"rx.snr\":" << tmpStr;
CP_GetLevelDemod(sid, "rssi", &tmpd);
sprintf(tmpStr, "%.2f", -tmpd);
result << ",\"rx.rssi\":" << tmpStr;
CP_GetDemodulatorParams(sid, "modcod", &tmpu32);
result << ",\"rx.modcod\":" << tmpu32;
CP_GetDemodulatorParams(sid, "type_pack", &tmpu32);
if (tmpu32) {
result << R"(,"rx.frameSize":"short")";
} else {
result << R"(,"rx.frameSize":"normal")";
}
CP_GetDemodulatorParams(sid, "is_pilots", &tmpu32);
if (tmpu32) {
result << R"(,"rx.pilots":"pilots")";
} else {
result << R"(,"rx.pilots":"no pilots")";
}
CP_GetLevelDemod(sid, "sym_err", &tmpd);
sprintf(tmpStr, "%.2f", tmpd);
result << ",\"rx.symError\":" << tmpStr;
CP_GetLevelDemod(sid, "crs_freq_err", &tmpd); // freqErr
sprintf(tmpStr, "%.2f", tmpd);
result << ",\"rx.freqErr\":" << tmpStr;
CP_GetLevelDemod(sid, "fine_freq_err", &tmpd); // freqErrAcc
sprintf(tmpStr, "%.2f", tmpd);
result << ",\"rx.freqErrAcc\":" << tmpStr;
CP_GetModulatorParams(sid, "if_overload", &tmpu32); // inputSignalLevel
result << ",\"rx.inputSignalLevel\":" << tmpStr;
CP_GetLevelDemod(sid, "afc_err", &tmpd); // PLL
sprintf(tmpStr, "%.2f", tmpd);
result << ",\"rx.pllError\":" << tmpStr;
std::string speed;
CP_GetDmaDebug(sid, "speed_rx", &speed);
sprintf(tmpStr, "%.3f", std::atof(speed.c_str()) / 128.0);
result << ",\"rx.speedOnRxKbit\":" << tmpStr;
speed.clear(); CP_GetDmaDebug(sid, "speed_rx_iface", &speed);
sprintf(tmpStr, "%.3f", std::atof(speed.c_str()) / 128.0);
result << ",\"rx.speedOnIifKbit\":" << tmpStr;
speed.clear(); CP_GetDmaDebug(sid, "packet_ok_rx", &speed);
result << ",\"rx.packetsOk\":" << std::atoi(speed.c_str());
speed.clear(); CP_GetDmaDebug(sid, "drop_bad_rx", &speed);
result << ",\"rx.packetsBad\":" << std::atoi(speed.c_str());
CP_GetDemodulatorParams(sid, "dummy_cnt", &tmpu32);
result << ",\"rx.packetsDummy\":" << tmpu32;
// auto reset_btn = m_lay->addWidget(std::make_unique<Wt::WPushButton>("Обновить"), 3, 0);
// reset_btn->setMaximumSize(95, 50);
// reset_btn->clicked().connect([=]
// {
// std::string var = "";
// CP_GetDmaDebug(sid, "reset_cnt_rx", &var);
// });
}
/*
stat_cinc: {
occ: '?',
correlator: '?',
correlatorFails: '?',
freqErr: '?', freqErrAcc: '?',
channelDelay: '?'
},
*/
// формируем структуру для CinC
{
std::string tmps;
char tmpbuff[32];
CP_GetDmaDebug(sid, "ratio_signal_signal", &tmps);
sprintf(tmpbuff, "%.3f", std::atof(tmps.c_str()));
result << ",\"cinc.occ\":" << tmpbuff;
const bool carrierOk = DriverCP_GetLevelDemod(sid, "carrier_lock");
if (isCinC && txEnable) {
if (carrierOk) {
result << R"(,"cinc.correlator":true)";
} else {
result << R"(,"cinc.correlator":false)";
}
} else {
result << R"(,"cinc.correlator":null)";
}
uint32_t tmpu32 = 0;
CP_GetDemodulatorParams(sid, "cnt_bad_lock_cinc", &tmpu32);
result << ",\"cinc.correlatorFails\":" << tmpu32;
tmps.clear(); CP_GetDmaDebug(sid, "freq_error_offset", &tmps);
// тут так сделано из-за приколов со знаками...
result << ",\"cinc.freqErr\":" << std::to_string(static_cast<int32_t>(std::atol(tmps.c_str())));
tmps.clear(); CP_GetDmaDebug(sid, "freq_fine_estimate", &tmps);
result << ",\"cinc.freqErrAcc\":" << tmps;
tmps.clear(); CP_GetDmaDebug(sid, "delay_dpdi", &tmps);
result << ",\"cinc.channelDelay\":" << std::to_string(static_cast<uint32_t>(std::round(std::atof(tmps.c_str()) * 1000)));
}
// структура температур девайса
{
char tmp[32];
double tmp_adrv_c = 0;
double tmp_ps = 0;
double tmp_pl = 0;
CP_ZynqParams(sid,"adrv-c_temper_celsius", &tmp_adrv_c);
CP_ZynqParams(sid,"pl_temper_celsius", &tmp_pl);
CP_ZynqParams(sid,"ps_temper_celsius", &tmp_ps);
sprintf(tmp, "%.1f", tmp_adrv_c / 1000); result << ",\"device.adrv\":" << tmp;
sprintf(tmp, "%.1f", tmp_pl / 1000); result << ",\"device.fpga\":" << tmp;
sprintf(tmp, "%.1f", tmp_ps / 1000); result << ",\"device.zync\":" << tmp;
}
result << "}";
return result.str();
auto res = state.asJson();
res["device"] = devState.asJson();
return res;
}
void api_driver::ApiDriver::resetPacketStatistics() const {
this->daemon->resetPacketStatistics();
}
nlohmann::json api_driver::ApiDriver::loadSettings() const {
if (daemon == nullptr) {
return R"({"error": "api daemon not started!"})";
}
nlohmann::json res = daemon->getSettingsRxTx().asJson();
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
res["qos"] = (daemon->getQosSettings().asJson());
#endif
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
res["network"] = (daemon->getNetworkSettings().asJson());
#endif
return res;
}
nlohmann::json api_driver::ApiDriver::loadFirmwareVersion() const {
if (daemon == nullptr) {
return R"({"error": "api daemon not started!"})";
}
return daemon->getFirmware().asJson();
}
void api_driver::ApiDriver::setRxTxSettings(const nlohmann::json& data) {
auto rxtx = daemon->getSettingsRxTx();
rxtx.updateMainSettings(data);
std::lock_guard _lapi(this->daemon->cpApiMutex);
this->daemon->cp.setDmaDebug("begin_save_config", "");
rxtx.storeMainSettings(this->daemon->cp);
this->daemon->cp.setDmaDebug("save_config", "");
rxtx.updateCallback(this->daemon->cp);
{
daemon->setSettingsRxTx(rxtx);
}
}
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
void api_driver::ApiDriver::setDpdiSettings(const nlohmann::json& data) {
auto rxtx = daemon->getSettingsRxTx();
rxtx.updateDpdiSettings(data);
std::lock_guard _lapi(this->daemon->cpApiMutex);
this->daemon->cp.setDmaDebug("begin_save_config", "");
rxtx.storeDpdiSettings(this->daemon->cp);
this->daemon->cp.setDmaDebug("save_config", "");
rxtx.updateCallback(this->daemon->cp);
{
daemon->setSettingsRxTx(rxtx);
}
}
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
void api_driver::ApiDriver::setBucLnbSettings(const nlohmann::json& data) {
auto rxtx = daemon->getSettingsRxTx();
rxtx.updateBuclnbSettings(data);
std::lock_guard _lapi(this->daemon->cpApiMutex);
this->daemon->cp.setDmaDebug("begin_save_config", "");
rxtx.storeBuclnbSettings(this->daemon->cp);
this->daemon->cp.setDmaDebug("save_config", "");
rxtx.updateCallback(this->daemon->cp);
{
daemon->setSettingsRxTx(rxtx);
}
}
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
void api_driver::ApiDriver::setQosSettings(const nlohmann::json& data) {
auto qos = daemon->getQosSettings();
qos.updateFromJson(data);
std::lock_guard _lapi(this->daemon->cpApiMutex);
this->daemon->cp.setDmaDebug("begin_save_config", "");
qos.store(this->daemon->cp);
this->daemon->cp.setDmaDebug("save_config", "");
qos.updateCallback(this->daemon->cp);
{
daemon->setQosSettings(qos);
}
}
#endif
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
void api_driver::ApiDriver::setNetworkSettings(const nlohmann::json& data) {
auto net = daemon->getNetworkSettings();
net.updateFromJson(data);
std::lock_guard _lapi(this->daemon->cpApiMutex);
this->daemon->cp.setDmaDebug("begin_save_config", "");
net.store(this->daemon->cp);
this->daemon->cp.setDmaDebug("save_config", "");
net.updateCallback(this->daemon->cp);
{
daemon->setNetworkSettings(net);
}
}
#endif
void api_driver::ApiDriver::resetDefaultSettings() {
daemon->resetDefaultSettings();
}
void api_driver::ApiDriver::executeInApi(const std::function<void(proxy::CpProxy&)>& callback) {
try {
std::lock_guard lock(this->daemon->cpApiMutex);
callback(this->daemon->cp);
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "ApiDriver::executeInApi(): failed to exec with error: " << e.what();
}
}
#ifdef MODEM_IS_TDMA
std::string api_driver::ApiDriver::getOtaFileLocation() const {
obj::TerminalDeviceState ds;
daemon->getDeviceState(ds);
return ds.fUpgradeImage;
}
#endif
#ifdef MODEM_IS_SCPC
nlohmann::json api_driver::ApiDriver::getLoggingStatisticsSettings() {
return this->daemon->statsLogs.getSettings();
}
void api_driver::ApiDriver::setLoggingStatisticsSettings(const nlohmann::json& data) {
this->daemon->statsLogs.setSettings(data);
}
#endif
api_driver::ApiDriver::~ApiDriver() = default;

View File

@@ -1,37 +1,113 @@
#ifndef TERMINAL_API_DRIVER_H
#define TERMINAL_API_DRIVER_H
#include "api-driver/stricts-enable.h"
#include "api-driver/proxy.h"
#include <memory>
#include <string>
#include <terminal_api/ControlProtoCInterface.h>
#include "common/nlohmann/json.hpp"
namespace api_driver {
constexpr int CACHE_STATISTICS_UPDATE_MS = 500; ///< время обновления кеша статистики модулятора/демодулятора
constexpr int CACHE_DEV_STATE_UPDATE_MS = 3000; ///< время обновления статуса устройства (обновления по воздуху, температуры)
constexpr int CACHE_SETTINGS_UPDATE_MS = 5000;
constexpr int CACHE_QOS_UPDATE_MS = 5000;
class TerminalApiDaemon;
/**
* Это ApiDriver. Все ответы он будет возвращать в виде json.
*/
* Это ApiDriver. Все ответы он будет возвращать в виде json.
*/
class ApiDriver {
public:
explicit ApiDriver();
/**
* Запросить общее состояние терминала
* @return {"txState":false,"rxState":false,"rx.sym_sync_lock":false,"rx.freq_search_lock":false,"rx.afc_lock":false,"rx.pkt_sync":false}
*/
std::string loadTerminalState();
* Запуск демона
*/
void startDaemon();
/**
* Запросить статистику модулятора, демодулятора, CicC и температурные датчики
* @return
* Запросить общее состояние терминала
*/
nlohmann::json loadTerminalState() const;
/**
* Сбросить статистику пакетов
*/
void resetPacketStatistics() const;
nlohmann::json loadSettings() const;
nlohmann::json loadFirmwareVersion() const;
/**
* Установить настройки RX/TX, readback можно получить используя loadTerminalState
*/
void setRxTxSettings(const nlohmann::json& data);
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
/**
* Установить настройки DPDI, readback можно получить используя loadTerminalState.
* @note Для TDMA и SCPC модемов эти настройки доступны
*/
void setDpdiSettings(const nlohmann::json& data);
#endif
#ifdef API_OBJECT_BUCLNB_SETTINGS_ENABLE
/**
* Установить настройки BUC и LNB, readback можно получить используя loadTerminalState.
*/
void setBucLnbSettings(const nlohmann::json& data);
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
/**
* Установить настройки QoS, readback можно получить используя loadTerminalState.
*/
void setQosSettings(const nlohmann::json& data);
#endif
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
void setNetworkSettings(const nlohmann::json& data);
#endif
void resetDefaultSettings();
void executeInApi(const std::function<void(proxy::CpProxy&)> &callback);
#ifdef API_OBJECT_DEBUG_METRICS_ENABLE
nlohmann::json getLoggingStatisticsSettings();
void setLoggingStatisticsSettings(const nlohmann::json& data);
/**
* Получить статистику в формате json. Выход будет дописан в вектор
* @param jsonOut вектор, куда должен быть записан результат. Данные будут дописаны к существующим, формат: []
* @param timeStart
* @param timeEnd
* @param ordering
* @param maxItems
*/
std::string loadDeviceStatistics();
void readLoggingStatistics(std::vector<uint8_t>& out, int timeStart = -1, int timeEnd = -1, bool ordering = false, int maxItems = -1);
#endif
#ifdef MODEM_IS_TDMA
std::string getOtaFileLocation() const;
#endif
~ApiDriver();
private:
TSID sid{0};
unsigned int access{0};
std::unique_ptr<TerminalApiDaemon> daemon;
};
/**
* Функция для создания экранированной строки (для json)
* @param source исходная строка (например, {123"})
* @return {"123\""}
*/
std::string buildEscapedString(const std::string &source);
}
#endif //TERMINAL_API_DRIVER_H

12
src/version.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef VERSION_H
#define VERSION_H
#ifndef PROJECT_GIT_REVISION
#warning "PROJECT_GIT_REVISION should be defined!"
#define PROJECT_GIT_REVISION "no-git"
#endif
#define PROJECT_BUILD_TIME __TIMESTAMP__ " compiler version " __VERSION__
#endif //VERSION_H

16
static/dev-params.json Normal file
View File

@@ -0,0 +1,16 @@
{
"params": [
{
"label": "Запись пакетов",
"name": "log_bool",
"widget": "checkbox",
"function": "DmaDebug"
},
{
"label": "Unused test",
"name": "log_bool",
"widget": "checkbox",
"function": "DmaDebug"
}
]
}

265
static/dev.html Normal file
View File

@@ -0,0 +1,265 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSCM-101</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/fields.css">
<style>
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: var(--bg-selected);
}
body { /* значение по-умолчанию */ --header-height: 60px; }
#content {
padding-top: var(--header-height);
}
.l3-proto-label > * {
display: inline-block;
}
.l3-proto-label input[type=checkbox] {
width: auto;
}
.
</style>
</head>
<body>
<div id="app" hidden>
<header>
<div class="tabs-header">
<span style="font-weight:bold">RSCM-101</span>
<a href="/#monitoring" class="tabs-btn">Мониторинг</a>
<a href="/#setup" class="tabs-btn">Настройки</a>
<a href="/#qos" class="tabs-btn">QoS</a>
<a href="/#admin" class="tabs-btn">Администрирование</a>
<a class="tabs-btn active">Разработчикам</a>
<a href="/logout" class="tabs-btn">Выход</a>
</div>
</header>
<div id="content">
<div class="settings-set-container">
<h2>Настройки записи пакетов в IPS</h2>
<label>
<span>Активировать запись</span>
<span class="toggle-input"><input type="checkbox" v-model="params.loggingIps" /><span class="slider"></span></span>
</label>
<button class="action-button" @click="settingsSubmitLoggingIps()">Сохранить <span class="submit-spinner" v-show="submitStatus.loggingIps"></span></button>
</div>
<div class="settings-set-container" v-if="settingFetchComplete">
<h2>Настройки журналирования статистики</h2>
<label>
<span>Активировать запись</span>
<span class="toggle-input"><input type="checkbox" v-model="params.loggingStatistics.en" /><span class="slider"></span></span>
</label>
<label>
<span>Период сбора статистики</span>
<select v-model="params.loggingStatistics.logPeriodMs">
<option :value="30000">30s</option>
<option :value="10000">10s</option>
<option :value="5000">5s</option>
<option :value="2000">2s</option>
<option :value="1000">1s</option>
<option :value="500">500ms</option>
<option :value="250">250ms (!)</option>
<option :value="50">FULL SPEED (!)</option>
</select>
</label>
<label>
<span>Время хранения</span>
<select v-model="params.loggingStatistics.maxAgeMs">
<option :value="60000">1 мин</option>
<option :value="300000">5 мин</option>
<option :value="900000">15 мин</option>
<option :value="1800000">30 мин</option>
<option :value="3600000">1 час</option>
<option :value="7200000">2 часа</option>
<option :value="21600000">6 часов</option>
<option :value="43200000">12 часов</option>
<option :value="86400000">24 часа</option>
<option :value="172800000">48 часов</option>
<option :value="604800000">7 суток</option>
</select>
</label>
<button class="action-button" @click="settingsSubmitLoggingStatistics()">Сохранить <span class="submit-spinner" v-show="submitStatus.loggingStatistics"></span></button>
</div>
<div class="settings-set-container">
<h2>Просмотр логов</h2>
<button class="action-button" @click="logView()">Обновить <span class="submit-spinner" v-show="submitStatus.logView"></span></button>
<a href="/dev/logs.csv" class="action-button" download>Скачать</a>
</div>
</div>
</div>
<div class="settings-set-container"><h2>График</h2><canvas id="mainChart" style="display: block; box-sizing: border-box;"></canvas></div>
<script src="/js/vue.js?v=3.5.13"></script>
<script src="/js/chart.js"></script>
<script src="/js/moment.js"></script>
<script src="/js/chartjs-adapter-moment.js"></script>
<script>
// для обновления высоты хидера
function updateHeaderHeight() { const header = document.querySelector('header'); document.body.style.setProperty('--header-height', `${header.offsetHeight}px`); }
window.addEventListener('load', updateHeaderHeight); window.addEventListener('resize', updateHeaderHeight);
let MainChart = new Chart(document.getElementById('mainChart').getContext('2d'), {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Время',
font: {
padding: 4,
size: 20,
weight: 'bold',
family: 'Arial'
},
//color: 'darkblue'
},
},
y: {
beginAtZero: true,
type: 'linear',
position: 'left'
}
}
}
})
const app = Vue.createApp({
data() {
return {
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
loggingIps: false,
loggingStatistics: false,
logView: false
},
params: {
loggingIps: false,
loggingStatistics: {
en: false,
logPeriodMs: 1000,
maxAgeMs: 0
}
},
chart: null,
settingFetchComplete: false
}
},
methods: {
settingsSubmitLoggingIps() {
if (this.submitStatus.loggingIps) { return }
this.submitStatus.loggingIps = true
fetch(`/dev/cpapicall?f=SetDmaDebug&param=log_bool&value=${this.params.loggingIps}`, {method: 'POST', headers: {'Content-Type': 'application/json'}, credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.loggingIps = false })
},
settingsSubmitLoggingStatistics() {
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
const modcods = [
"DUMMY",
"QPSK 1/4", "QPSK 1/3", "QPSK 2/5", "QPSK 1/2", "QPSK 3/5", "QPSK 2/3", "QPSK 3/4", "QPSK 4/5", "QPSK 5/6", "QPSK 8/9", "QPSK 9/10",
"8PSK 3/5", "8PSK 2/3", "8PSK 3/4", "8PSK 5/6", "8PSK 8/9", "8PSK 9/10",
"16APSK 2/3", "16APSK 3/4", "16APSK 4/5", "16APSK 5/6", "16APSK 8/9", "16APSK 9/10",
"32APSK 3/4", "32APSK 4/5", "32APSK 5/6", "32APSK 8/9", "32APSK 9/10",
]
if (typeof modcod != "number") {
return "?";
}
if (modcod < 0 || modcod >= modcods.length) {
return `? (${modcod})`
}
return modcods[modcod]
}
if (this.submitStatus.loggingStatistics) { return }
let query = {
"en": this.params.loggingStatistics.en,
"logPeriodMs": this.params.loggingStatistics.logPeriodMs,
"maxAgeMs": this.params.loggingStatistics.maxAgeMs,
}
this.submitStatus.loggingStatistics = true
fetch('/dev/settings', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateLoggingStatisticsSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.loggingStatistics = false })
},
logView() {
if (this.submitStatus.logView) { return }
this.submitStatus.logView = true
fetch(`/dev/logs.csv`, {method: 'GET', credentials: 'same-origin' })
.then(async (resp) => {
let logfileContent = await resp.text()
const lines = logfileContent.trim().split(/\r\n|\n/)
let datasets = []
// столбец timestamp пропускаем
let labels = lines.shift().split('\t')
labels.shift()
let rows = lines.map(line => line.split('\t'))
for (let li = 0; li < labels.length; li++) {
let td = []
for (let ri = 0; ri < rows.length; ri++) {
td.push({x: new Date(rows[ri][0]), y: rows[ri][li + 1]})
}
datasets.push({
label: labels[li],
data: td,
//borderColor: 'blue',
borderWidth: 2,
fill: false,
})
}
MainChart.datasets = datasets
MainChart.update()
})
.catch((reason) => { alert(`Ошибка при чтении логов: ${reason}`) })
.finally(() => { this.submitStatus.logView = false })
},
updateLoggingStatisticsSettings(vals) {
this.params.loggingStatistics.en = vals['logstat']['en']
this.params.loggingStatistics.logPeriodMs = vals['logstat']['logPeriodMs']
this.params.loggingStatistics.maxAgeMs = vals['logstat']['maxAgeMs']
}
},
mounted() {
const doFetchSettings = async () => {
let d = await fetch("/dev/settings")
let vals = await d.json()
this.settingFetchComplete = true
this.updateLoggingStatisticsSettings(vals)
}
doFetchSettings().then(() => {})
document.getElementById("app").removeAttribute("hidden")
//this.chart = chart
}
});
app.mount('#app')
</script>
</body>
</html>

202
static/fields.css Normal file
View File

@@ -0,0 +1,202 @@
.tabs-header {
margin: 0.5em 0 3px;
background: var(--brand-bg);
}
.tabs-header > * {
display: inline-block;
}
.tabs-btn {
text-decoration: none;
border: none;
padding: 10px 25px;
text-align: center;
cursor: pointer;
margin-bottom: -3px;
border-bottom: 3px solid #eee;
}
.tabs-btn.active {
color: var(--brand-text);
border-bottom: 3px solid var(--bg-action);
}
.tabs-body-item {
padding: 20px 0;
}
.indicator_bad {
background: var(--text-bad);
}
.indicator_good {
background: var(--text-good);
}
.indicator {
display: inline-block;
width: 1em;
height: 1em;
border: solid 1px var(--text-color2);
border-radius: 0.5em;
}
.dangerous-button, .action-button {
border: solid 1px var(--text-color2);
border-radius: 0.5em;
padding: 0.3em;
margin: 0.2em;
}
.summary-actions {
display: flex;
flex-direction: row;
align-items: stretch;
}
.summary-actions * {
margin: 0;
}
.dangerous-button {
background: var(--bg-danger);
}
.action-button {
background: var(--bg-action);
}
.nav-bar-element {
margin: 0.5em;
border-bottom: 2px solid var(--text-color2);
}
.tabs-item-flex-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.tabs-item-flex-container > * {
flex: 1 1 auto
}
.tabs-item-flex-container > *, .settings-set-container {
margin: 1em;
border: 1px solid var(--text-color2);
border-radius: 0.2em;
}
.settings-set-container {
padding: 1em;
}
.statistics-container th {
text-align: left;
font-weight: normal;
}
th { padding: 0.2em 1em 0.2em 0.2em; }
.statistics-container td { min-width: 10em; }
td { padding: 0.2em; }
.tabs-item-flex-container h2 {
margin-top: 0;
}
.settings-set-container > h3 {
margin: 0;
}
label * {
display: block;
}
label {
margin: 1em 0.5em;
display: block;
/*background: var(--bg-selected);*/
color: var(--text-color2);
}
.settings-set-container input, .settings-set-container select {
margin-top: 0.5em;
border: none;
border-bottom: solid 2px var(--text-color2);
width: 20em;
box-sizing: border-box;
}
.settings-set-container input:focus {
outline: none;
border: none;
border-bottom: solid 2px var(--bg-action);
}
.settings-set-container input:invalid {
border: solid 1px var(--text-bad);
}
/* костыль для браузеров, которые некорректно стилизуют элементы option */
select * {
background: var(--bg-selected);
color: var(--text-color);
}
.settings-set-container th, .settings-set-container td {
border-bottom: solid 1px var(--bg-element);
}
.settings-set-container table { border-collapse: collapse; }
.settings-set-container tr:hover {
background: var(--bg-selected);
}
details > summary {
display: flex;
justify-content: space-between;
align-items: stretch;
}
.submit-spinner {
display: inline-block;
width: 1em;
height: 1em;
border: 3px solid #f3f3f3; /* Цвет границы */
border-radius: 50%; /* Делаем круг */
border-top-color: #3498db; /* Цвет верхней границы */
animation: spin 0.8s linear infinite; /* Анимация вращения */
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/*********************** Стили для красивых 'switch' ***********************/
.toggle-input {
position: relative;
margin: 5px 10px;
width: 50px;
height: 16px;
background-color: var(--bg-element);
border-radius: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.toggle-input input[type="checkbox"] {
display: none;
}
.toggle-input .slider {
position: absolute;
top: -4px;
left: 0;
width: 25px;
height: 25px;
background-color: var(--text-color);
border-radius: 50%;
transition: left 0.3s;
}
.toggle-input input[type="checkbox"]:checked + .slider {
left: 25px;
background-color: var(--bg-action);
}
.toggle-input input[type="checkbox"]:checked + .slider:before {
left: 10px;
}

BIN
static/internet.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

20
static/js/chart.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
/*!
* chartjs-adapter-moment v1.0.1
* https://www.chartjs.org
* (c) 2022 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));

15
static/js/moment.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

9
static/js/vue.prod.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,99 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSCM-101 | Вход</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
#form-wrapper {
overflow: hidden;
max-width: 27em;
margin: 5em auto;
height: auto;
text-align: center;
}
.form-row {
padding: 4px 0;
margin: 1.5em;
}
.form-row * {
font-size: 1em;
text-align: left;
display: block;
}
.form-row label {
line-height: 2em;
font-weight: bolder;
}
.form-row input {
padding: 8px;
width: 100%;
box-sizing: border-box;
border: none;
border-bottom: var(--brand-bg) 2px solid;
background-color: var(--bg-color);
text-overflow: ellipsis;
min-height: 2em;
}
.form-row input:focus {
outline: none;
border: none;
border-bottom: var(--brand-text) 2px solid;
background-color: var(--bg-selected);
}
#submit {
border: none;
font-weight: bolder;
background: var(--bg-action);
text-align: center;
}
</style>
</head>
<body>
<div id="form-wrapper">
<h1> Вход </h1>
<form method="POST" id="login-form">
{% csrf_token %}
<div class="form-row value-bad">
Неверный логин или пароль
</div>
<div class="form-row">
<label for="username">Имя пользователя</label>
<input type="text" name="username" id="username" required/>
</div>
<div class="form-row">
<label for="password">Пароль</label>
<input type="password" name="password" id="password" required/>
</div>
<div class="form-row">
<input id="submit" type="submit" value="Войти">
</div>
</form>
</div>
<script>
document.getElementById("username").onkeydown = (e) => {
if (e.key === 'Enter') {
document.getElementById("password").focus()
}
}
document.getElementById("password").onkeydown = (e) => {
if (e.key === 'Enter') {
document.getElementById("login-form").submit()
}
}
</script>
</body>
</html>

View File

@@ -35,7 +35,7 @@
width: 100%;
box-sizing: border-box;
border: none;
border-bottom: var(--brand-bg) 2px solid;
border-bottom: var(--text-color2) 2px solid;
background-color: var(--bg-color);
text-overflow: ellipsis;
min-height: 2em;
@@ -44,7 +44,7 @@
.form-row input:focus {
outline: none;
border: none;
border-bottom: var(--brand-text) 2px solid;
border-bottom: var(--bg-action) 2px solid;
background-color: var(--bg-selected);
}
@@ -52,6 +52,7 @@
border: none;
font-weight: bolder;
background: var(--bg-action);
color: var(--text-color);
text-align: center;
}
@@ -61,13 +62,8 @@
<div id="form-wrapper">
<h1> Вход </h1>
<form method="POST" id="login-form">
<!-- {% csrf_token %}-->
<!-- {% if message %}-->
<!-- <div class="form-row value-bad">-->
<!-- {{ message }}-->
<!-- </div>-->
<!-- {% endif %}-->
<form id="login-form" onsubmit="submitLoginForm(); return false">
<div class="form-row value-bad" hidden id="form-error-message"></div>
<div class="form-row">
<label for="username">Имя пользователя</label>
@@ -97,6 +93,42 @@
document.getElementById("login-form").submit()
}
}
const loginForm = document.getElementById('login-form');
const formErrorMessage = document.getElementById('form-error-message')
function submitLoginForm() {
const username = document.getElementById('username').value
const password = document.getElementById('password').value
const requestData = {
"username": username,
"password": password
};
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData),
credentials: 'same-origin'
}).then(response => {
// Обработка ответа сервера
response.json().then((value) => {
if (value["error"]) {
formErrorMessage.innerText = value["error"]
formErrorMessage.removeAttribute("hidden")
} else {
window.location = "/"
}
})
}).catch(error => {
formErrorMessage.innerText = error
formErrorMessage.removeAttribute("hidden")
console.error('Ошибка отправки запроса:', error) // Обработка ошибки отправки запроса
})
}
</script>
</body>
</html>

1563
static/main-scpc.html Normal file

File diff suppressed because it is too large Load Diff

972
static/main-shps.html Normal file
View File

@@ -0,0 +1,972 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ШПС Модем</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/fields.css">
<style>
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: var(--bg-selected);
}
body { /* значение по-умолчанию */ --header-height: 60px; }
#content {
padding-top: var(--header-height);
}
.l3-proto-label {
margin: 0 0 0 0.5em;
}
.l3-proto-label > * {
display: inline-block;
}
.l3-proto-label input[type=checkbox] {
width: auto;
}
</style>
</head>
<body>
<div id="app" hidden>
<header>
<span class="nav-bar-element">Прием: <span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Передача: <span :class="{ indicator_good: statTx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Тест: <span :class="{ indicator_good: testState, indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
<span :class="{ value_bad: initState !== 'Успешная инициализация системы' }">{{ initState }}</span>
<div class="tabs-header">
<span style="font-weight:bold">ШПС Модем</span>
<a href="#monitoring" class="tabs-btn" @click="activeTab = 'monitoring'" :class="{ active: activeTab === 'monitoring' }">Мониторинг</a>
<a href="#setup" class="tabs-btn" @click="activeTab = 'setup'" :class="{ active: activeTab === 'setup' }">Настройки</a>
<a href="#admin" class="tabs-btn" @click="activeTab = 'admin'" :class="{ active: activeTab === 'admin' }">Администрирование</a>
<a href="/logout" class="tabs-btn">Выход</a>
</div>
</header>
<div id="content">
<div class="tabs-body-item tabs-item-flex-container" v-if="activeTab === 'monitoring'">
<div class="settings-set-container statistics-container">
<h2>Статистика приема</h2>
<table>
<tbody>
<tr><th>Прием</th><td><span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ statRx.snr }} / {{ statRx.rssi }}</td></tr>
<tr><th>Частотная ошибка, Гц</th><td>{{ statRx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ statRx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ statRx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ statRx.speedOnRxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statRx.speedOnIifKbit }} кбит/с</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статистика пакетов</td></tr>
<tr><th>Качественных пакетов</th><td>{{ statRx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ statRx.packetsBad }}</td></tr>
</tbody>
</table>
<button class="action-button" @click="resetPacketsStatistics()"> Сброс статистики </button>
</div>
<div class="settings-set-container statistics-container">
<h2>Статистика передачи</h2>
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
<tr><th>Центральная частота</th><td>{{ statTx.centerFreq }} кГц</td></tr>
<tr><th>Символьная скорость</th><td>{{ statTx.symSpeed }} ksymb</td></tr>
</tbody>
</table>
</div>
<div class="settings-set-container statistics-container">
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ statDevice.adrv }} °C</td></tr>
<tr><th>Температура ZYNQ</th><td>{{ statDevice.zynq }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ statDevice.fpga }} °C</td></tr>
<tr><th>Время работы устройства</th><td>{{ statOs.uptime }}</td></tr>
<tr><th>Средняя загрузка ЦП (1/5/15 мин.)</th><td>{{ statOs.load1 }}% {{ statOs.load5 }}% {{ statOs.load15 }}%</td></tr>
<tr><th>ОЗУ всего/свободно</th><td>{{ statOs.totalram }}МБ/{{ statOs.freeram }}МБ</td></tr>
</tbody>
</table>
</div>
</div>
<div class="tabs-body-item" v-if="activeTab === 'setup' && settingFetchComplete">
<h2>Настройки приема/передачи</h2>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки передатчика</h3>
<label>
<span>Включить передатчик</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.txEn" /><span class="slider"></span></span>
</label>
<label>
<span>Автоматический запуск передатчика</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.txAutoStart" /><span class="slider"></span></span>
</label>
<label>
<span>Режим работы модулятора</span>
<select v-model="paramRxtx.txModulatorIsTest">
<option :value="false">Нормальный</option>
<option :value="true">Тест (CW)</option>
</select>
</label>
<label><span>Ослабление, дБ</span><input type="number" v-model="paramRxtx.txAttenuation" min="-40" step="0.25"/></label>
<label>
<span>Входные данные</span>
<select v-model="paramRxtx.txIsTestInput">
<option :value="false">Ethernet</option>
<option :value="true">Тест</option>
</select>
</label>
<h3>Параметры передачи</h3>
<label>
<span>Центральная частота, КГц</span>
<input type="text" v-model.lazy="paramRxtx.txCentralFreq" @change="e => paramRxtx.txCentralFreq = inputFormatNumber(inputFormatNumber(e.target.value, {min:70000,max:6000000,step:100}), {min:70000,max:6000000,step:100})"/>
</label>
<label>
<span>Символьная скорость, Бод</span>
<input type="text" v-model.lazy="paramRxtx.txBaudrate" @change="e => paramRxtx.txBaudrate = inputFormatNumber(inputFormatNumber(e.target.value, {min:128000,max:30000000,}), {min:128000,max:30000000,})"/>
</label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.txRolloff">
<option :value="20">0.02</option>
<option :value="50">0.05</option>
<option :value="100">0.10</option>
<option :value="150">0.15</option>
<option :value="200">0.20</option>
<option :value="250">0.25</option>
<option :value="300">0.30</option>
<option :value="350">0.35</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.txSpreadCoef" min="8" max="1024" step="2"/></label>
<label><span>Кол-во пакетов на преамбулу</span><input type="number" v-model="paramRxtx.txFieldsDataPreamble" min="1" max="255" step="1"/></label>
</div>
<div class="settings-set-container">
<h3>Авто-регулировка мощности</h3>
<label>
<span>Авто-регулировка мощности</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.aupcEn" /><span class="slider"></span></span>
</label>
<label><span>Минимальное ослабление, дБ</span><input type="number" v-model="paramRxtx.aupcMinAttenuation" max="10" step="0.1"/></label>
<label><span>Максимальное ослабление, дБ</span><input type="number" v-model="paramRxtx.aupcMaxAttenuation" max="10" step="0.1"/></label>
<label><span>Требуемое ОСШ</span><input type="number" v-model="paramRxtx.aupcRequiredSnr" max="30" step="0.01"/></label>
</div>
<div class="settings-set-container">
<h3>Настройки приемника</h3>
<label>
<span>Режим управления усилением</span>
<select v-model="paramRxtx.rxAgcEn">
<option :value="false">РРУ</option>
<option :value="true">АРУ</option>
</select>
</label>
<label v-show="paramRxtx.rxAgcEn === false"><span>Усиление, дБ</span><input type="number" v-model="paramRxtx.rxManualGain" min="-40" max="40" step="0.01"/></label>
<label v-show="paramRxtx.rxAgcEn === true">
<span>Текущее усиление</span><span>{{ paramRxtx.rxManualGain }}</span>
</label>
<label>
<span>Инверсия спектра</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.rxSpectrumInversion" /><span class="slider"></span></span>
</label>
<label>
<span>Центральная частота, КГц</span>
<input type="text" v-model.lazy="paramRxtx.rxCentralFreq" @change="e => paramRxtx.rxCentralFreq = inputFormatNumber(inputFormatNumber(e.target.value, {min:70000,max:6000000,step:100}), {min:70000,max:6000000,step:100})"/>
</label>
<label>
<span>Символьная скорость, Бод</span>
<input type="text" v-model.lazy="paramRxtx.rxBaudrate" @change="e => paramRxtx.rxBaudrate = inputFormatNumber(inputFormatNumber(e.target.value, {min:128000,max:30000000,}), {min:128000,max:30000000,})"/>
</label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.rxRolloff">
<option :value="20">0.02</option>
<option :value="50">0.05</option>
<option :value="100">0.10</option>
<option :value="150">0.15</option>
<option :value="200">0.20</option>
<option :value="250">0.25</option>
<option :value="300">0.30</option>
<option :value="350">0.35</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.rxSpreadCoef" min="8" max="1024" step="2"/></label>
<label><span>Порог коррелятора</span><input type="number" v-model="paramRxtx.rxFftShift" min="1" max="10" step="0.125"/></label>
<label><span>Кол-во пакетов на преамбулу</span><input type="number" v-model="paramRxtx.rxFieldsDataPreamble" min="1" max="255" step="1"/></label>
</div>
</div>
<button class="action-button" @click="settingsSubmitRxtx()">Сохранить <span class="submit-spinner" v-show="submitStatus.rxtx"></span></button>
<h2>Настройки питания и опорного генератора</h2>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки BUC</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.bucRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание BUC</span>
<select v-model="paramBuclnb.bucPowering">
<option :value="0">Выкл</option>
<option :value="24">24В</option>
<option :value="48">48В</option>
</select>
</label>
</div>
<div class="settings-set-container">
<h3>Настройки LNB</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.lnbRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание LNB</span>
<select v-model="paramBuclnb.lnbPowering">
<option :value="0">Выкл</option>
<option :value="13">13В</option>
<option :value="18">18В</option>
<option :value="24">24В</option>
</select>
</label>
</div>
<div class="settings-set-container">
<h3>Сервисные настройки</h3>
<label>
<span>Подача опоры 10МГц на 'Выход 10МГц'</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.srvRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Автозапуск BUC и LNB при включении</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.bucLnbAutoStart" /><span class="slider"></span></span>
</label>
</div>
</div>
<button class="action-button" @click="settingsSubmitBuclnb()">Сохранить <span class="submit-spinner" v-show="submitStatus.buclnb"></span></button>
</div> <div class="tabs-body-item" v-if="activeTab === 'admin' && settingFetchComplete">
<h2>Настройки сети</h2>
<div class="settings-set-container">
<h3>Настройки интерфейса управления</h3>
<label>
<span>Интерфейс управления (a.d.d.r/mask)</span>
<input v-model="paramNetwork.managementIp" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$">
</label>
<label>
<span>Режим сети</span>
<select v-model="paramNetwork.isL2">
<option :value="false">Маршрутизатор</option>
<option :value="true">Коммутатор</option>
</select>
</label>
<label v-show="paramNetwork.isL2 === false">
<span>Интерфейс данных (/24)</span>
<input v-model="paramNetwork.dataIp" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</label>
<label><span>MTU интерфейса данных</span><input type="number" v-model="paramNetwork.dataMtu" min="1500" max="2000" step="1"/></label>
<label>
<span>Имя веб-сервера</span>
<input v-model="paramNetwork.serverName" type="text">
</label>
</div>
<button class="action-button" @click="settingsSubmitNetwork()">Сохранить <span class="submit-spinner" v-show="submitStatus.network"></span></button>
<h2>Система</h2>
<div class="settings-set-container statistics-container">
<table>
<tbody>
<tr><th>Версия ПО</th><td>{{ about.firmwareVersion }}</td></tr>
<tr><th>ID модема</th><td>{{ about.modemUid }}</td></tr>
<tr><th>Серийный номер</th><td>{{ about.modemSn }}</td></tr>
<tr><th>MAC интерфейса управления</th><td>{{ about.macManagement }}</td></tr>
<tr><th>MAC интерфейса данных</th><td>{{ about.macData }}</td></tr>
</tbody>
</table>
<div>
<button class="dangerous-button" @click="doModemReboot()">Перезагрузить модем <span class="submit-spinner" v-show="submitStatus.modemReboot !== null"></span></button>
</div>
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>
<div class="settings-set-container statistics-container">
<h3>Ручное обновление</h3>
<label>
<span>Файл {{ this.uploadFw.progress !== null ? `(${this.uploadFw.progress}%)` : '' }}</span>
<input type="file" accept="application/zip" @change="(e) => { this.uploadFw.filename = e.target.files[0] }">
<span v-if="uploadFw.sha256 !== null">SHA256: {{ uploadFw.sha256 }}</span>
</label>
<button class="action-button" @click="settingsUploadUpdate()">Загрузить<span class="submit-spinner" v-show="submitStatus.firmwareUpload"></span></button>
<button class="dangerous-button" v-show="uploadFw.sha256 !== null" @click="settingsPerformFirmwareUpgrade()">Обновить встроенное ПО<span class="submit-spinner" v-show="submitStatus.firmwareUpgrade"></span></button>
</div>
</div>
<p>Последнее обновление статистики: {{ lastUpdateTime }}</p>
</div>
</div>
<script src="/js/vue.js?v=3.5.13"></script>
<script>
const availableTabs = ['monitoring', 'setup', 'admin']
// для обновления высоты хидера
function updateHeaderHeight() { const header = document.querySelector('header'); document.body.style.setProperty('--header-height', `${header.offsetHeight}px`); }
window.addEventListener('load', updateHeaderHeight); window.addEventListener('resize', updateHeaderHeight);
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return availableTabs[0]
}
function toLocaleStringWithSpaces(num) {
if (typeof num !== 'number') {
if (typeof num === 'string') { return num }
return String(num);
}
const numberString = num.toString()
const [integerPart, fractionalPart] = numberString.split('.')
const spacedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
if (fractionalPart) { return `${spacedIntegerPart}.${fractionalPart}` }
else { return spacedIntegerPart }
}
const app = Vue.createApp({
data() {
return {
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
rxtx: false,
buclnb: false,
network: false,
firmwareUpload: false,
firmwareUpgrade: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
// ========== include from 'common/all-params-data.js.j2'
paramRxtx: {
txEn: false,
txAutoStart: false,
txModulatorIsTest: false,
txAttenuation: -40,
txIsTestInput: false,
txCentralFreq: 0,
txBaudrate: 0,
txRolloff: 20,
txSpreadCoef: 8,
txFieldsDataPreamble: 1,
aupcEn: false,
aupcMinAttenuation: 0,
aupcMaxAttenuation: 0,
aupcRequiredSnr: 0,
rxAgcEn: false,
rxManualGain: -40,
rxSpectrumInversion: false,
rxCentralFreq: 0,
rxBaudrate: 0,
rxRolloff: 20,
rxSpreadCoef: 8,
rxFftShift: 1,
rxFieldsDataPreamble: 1,
},
paramBuclnb: {
bucRefClk10M: false,
bucPowering: 0,
lnbRefClk10M: false,
lnbPowering: 0,
srvRefClk10M: false,
bucLnbAutoStart: false,
},
paramNetwork: {
managementIp: null,
isL2: false,
dataIp: null,
dataMtu: 1500,
serverName: null,
},
// ========== include end from 'common/all-params-data.js.j2'
// ========== include from 'common/monitoring-data.js.j2'
statRx: {
// индикаторы
state: '?', // общее состояние
sym_sync_lock: '?', // захват символьной
freq_search_lock: '?', // Захват поиска по частоте
afc_lock: '?', // захват ФАПЧ
pkt_sync: '?', // захват пакетной синхронизации
// куча других параметров, идет в том же порядке, что и в таблице
snr: '?', rssi: '?',
modcod: '?', frameSizeNormal: '?',
isPilots: '?',
symError: '?',
freqErr: '?', freqErrAcc: '?',
inputSignalLevel: '?',
pllError: '?',
speedOnRxKbit: '?',
speedOnIifKbit: '?',
// статистика пакетов
packetsOk: '?', packetsBad: '?', packetsDummy: '?',
},
statTx: {
// состояние
state: '?',
// прочие поля
modcod: '?', speedOnTxKbit: '?', speedOnIifKbit: '?', centerFreq: '?', symSpeed: '?'
},
statDevice: { // температурные датчики
adrv: 0, zynq: 0, fpga: 0
},
statOs: {uptime: '?', load1: '?', load5: '?', load15: '?', totalram: '?', freeram: '?'},
// ========== include end from 'common/monitoring-data.js.j2'
// ========== include from 'common/setup-data.js.j2'
// ========== include end from 'common/setup-data.js.j2'
// ========== include from 'common/admin-data.js.j2'
// ========== include end from 'common/admin-data.js.j2'
uploadFw: {
progress: null,
filename: null,
sha256: null
},
// эти "настройки" - read only
about: {
firmwareVersion: '?',
modemUid: '?',
modemSn: '?',
macManagement: '?',
macData: '?',
},
testState: false,
initState: '',
lastUpdateTime: new Date(),
activeTab: getCurrentTab(),
settingFetchComplete: false,
}
},
methods: {
correctModcodSpeed(modulation, speed) {
const mod = modulation.toLowerCase()
const available = {
"qpsk": ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"8psk": ['2/3', '3/4', '5/6', '8/9', '9/10'],
"16apsk": ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"32apsk": ['3/4', '4/5', '5/6', '8/9', '9/10']
}
if (mod in available) {
if (available[mod].indexOf(speed) >= 0) {
return speed
}
return available[mod][0]
}
return ""
},
getAvailableModcods(modulation) {
// NOTE модкоды со скоростью хода 3/5 не работают
switch (modulation) {
case 'qpsk':
return ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '8psk':
return ['3/5', '2/3', '3/4', '5/6', '8/9', '9/10']
case '16apsk':
return ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '32apsk':
return ['3/4', '4/5', '5/6', '8/9', '9/10']
default:
return []
}
},
inputFormatNumber(src, validation) {
if (validation === null || validation === undefined) { validation = {} }
const rawVal = src.toString().replace(/[^0-9.,]/g, '').replace(',', '.')
let result = rawVal === '' ? 0 : parseFloat(rawVal)
const step = 'step' in validation ? validation['step']: 1.0
result = Math.round(result / step) * step
if ('min' in validation) { if (result <= validation['min']) { result = validation['min'] } }
if ('max' in validation) { if (result >= validation['max']) { result = validation['max'] } }
return toLocaleStringWithSpaces(result)
},
// ========== include from 'common/all-params-methods.js.j2'
settingsSubmitRxtx() {
if (this.submitStatus.rxtx) { return }
let query = {
"txEn": this.paramRxtx.txEn,
"txAutoStart": this.paramRxtx.txAutoStart,
"txModulatorIsTest": this.paramRxtx.txModulatorIsTest,
"txAttenuation": this.paramRxtx.txAttenuation,
"txIsTestInput": this.paramRxtx.txIsTestInput,
"txCentralFreq": parseFloat(this.paramRxtx.txCentralFreq.replace(/[^0-9,.]/g, '').replace(',', '.')),
"txBaudrate": parseFloat(this.paramRxtx.txBaudrate.replace(/[^0-9,.]/g, '').replace(',', '.')),
"txRolloff": this.paramRxtx.txRolloff,
"txSpreadCoef": this.paramRxtx.txSpreadCoef,
"txFieldsDataPreamble": this.paramRxtx.txFieldsDataPreamble,
"aupcEn": this.paramRxtx.aupcEn,
"aupcMinAttenuation": this.paramRxtx.aupcMinAttenuation,
"aupcMaxAttenuation": this.paramRxtx.aupcMaxAttenuation,
"aupcRequiredSnr": this.paramRxtx.aupcRequiredSnr,
"rxAgcEn": this.paramRxtx.rxAgcEn,
"rxManualGain": this.paramRxtx.rxManualGain,
"rxSpectrumInversion": this.paramRxtx.rxSpectrumInversion,
"rxCentralFreq": parseFloat(this.paramRxtx.rxCentralFreq.replace(/[^0-9,.]/g, '').replace(',', '.')),
"rxBaudrate": parseFloat(this.paramRxtx.rxBaudrate.replace(/[^0-9,.]/g, '').replace(',', '.')),
"rxRolloff": this.paramRxtx.rxRolloff,
"rxSpreadCoef": this.paramRxtx.rxSpreadCoef,
"rxFftShift": this.paramRxtx.rxFftShift,
"rxFieldsDataPreamble": this.paramRxtx.rxFieldsDataPreamble,
}
this.submitStatus.rxtx = true
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateRxtxSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.rxtx = false })
},
settingsSubmitBuclnb() {
if (this.submitStatus.buclnb) { return }
{ if (!confirm("Применение неправильных настроек может вывести из строя оборудование! Продолжить?")) return }
let query = {
"bucRefClk10M": this.paramBuclnb.bucRefClk10M,
"bucPowering": this.paramBuclnb.bucPowering,
"lnbRefClk10M": this.paramBuclnb.lnbRefClk10M,
"lnbPowering": this.paramBuclnb.lnbPowering,
"srvRefClk10M": this.paramBuclnb.srvRefClk10M,
"bucLnbAutoStart": this.paramBuclnb.bucLnbAutoStart,
}
this.submitStatus.buclnb = true
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateBuclnbSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.buclnb = false })
},
settingsSubmitNetwork() {
if (this.submitStatus.network) { return }
{ if (!confirm("Применение этих настроек может сделать модем недоступным! Продолжить?")) return }
let query = {
"managementIp": this.paramNetwork.managementIp,
"isL2": this.paramNetwork.isL2,
"dataIp": this.paramNetwork.dataIp,
"dataMtu": this.paramNetwork.dataMtu,
"serverName": this.paramNetwork.serverName,
}
this.submitStatus.network = true
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateNetworkSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.network = false })
},
updateRxtxSettings(vals) {
this.submitStatus.rxtx = false
this.paramRxtx.txEn = vals["settings"]["rxtx"]["txEn"]
this.paramRxtx.txAutoStart = vals["settings"]["rxtx"]["txAutoStart"]
this.paramRxtx.txModulatorIsTest = vals["settings"]["rxtx"]["txModulatorIsTest"]
this.paramRxtx.txAttenuation = vals["settings"]["rxtx"]["txAttenuation"]
this.paramRxtx.txIsTestInput = vals["settings"]["rxtx"]["txIsTestInput"]
this.paramRxtx.txCentralFreq = this.inputFormatNumber(vals["settings"]["rxtx"]["txCentralFreq"], {min:70000,max:6000000,step:100})
this.paramRxtx.txBaudrate = this.inputFormatNumber(vals["settings"]["rxtx"]["txBaudrate"], {min:128000,max:30000000,})
this.paramRxtx.txRolloff = vals["settings"]["rxtx"]["txRolloff"]
this.paramRxtx.txSpreadCoef = vals["settings"]["rxtx"]["txSpreadCoef"]
this.paramRxtx.txFieldsDataPreamble = vals["settings"]["rxtx"]["txFieldsDataPreamble"]
this.paramRxtx.aupcEn = vals["settings"]["rxtx"]["aupcEn"]
this.paramRxtx.aupcMinAttenuation = vals["settings"]["rxtx"]["aupcMinAttenuation"]
this.paramRxtx.aupcMaxAttenuation = vals["settings"]["rxtx"]["aupcMaxAttenuation"]
this.paramRxtx.aupcRequiredSnr = vals["settings"]["rxtx"]["aupcRequiredSnr"]
this.paramRxtx.rxAgcEn = vals["settings"]["rxtx"]["rxAgcEn"]
this.paramRxtx.rxManualGain = vals["settings"]["rxtx"]["rxManualGain"]
this.paramRxtx.rxSpectrumInversion = vals["settings"]["rxtx"]["rxSpectrumInversion"]
this.paramRxtx.rxCentralFreq = this.inputFormatNumber(vals["settings"]["rxtx"]["rxCentralFreq"], {min:70000,max:6000000,step:100})
this.paramRxtx.rxBaudrate = this.inputFormatNumber(vals["settings"]["rxtx"]["rxBaudrate"], {min:128000,max:30000000,})
this.paramRxtx.rxRolloff = vals["settings"]["rxtx"]["rxRolloff"]
this.paramRxtx.rxSpreadCoef = vals["settings"]["rxtx"]["rxSpreadCoef"]
this.paramRxtx.rxFftShift = vals["settings"]["rxtx"]["rxFftShift"]
this.paramRxtx.rxFieldsDataPreamble = vals["settings"]["rxtx"]["rxFieldsDataPreamble"]
},
updateBuclnbSettings(vals) {
this.submitStatus.buclnb = false
this.paramBuclnb.bucRefClk10M = vals["settings"]["buclnb"]["bucRefClk10M"]
this.paramBuclnb.bucPowering = vals["settings"]["buclnb"]["bucPowering"]
this.paramBuclnb.lnbRefClk10M = vals["settings"]["buclnb"]["lnbRefClk10M"]
this.paramBuclnb.lnbPowering = vals["settings"]["buclnb"]["lnbPowering"]
this.paramBuclnb.srvRefClk10M = vals["settings"]["buclnb"]["srvRefClk10M"]
this.paramBuclnb.bucLnbAutoStart = vals["settings"]["buclnb"]["bucLnbAutoStart"]
},
updateNetworkSettings(vals) {
this.submitStatus.network = false
this.paramNetwork.managementIp = vals["settings"]["network"]["managementIp"]
this.paramNetwork.isL2 = vals["settings"]["network"]["isL2"]
this.paramNetwork.dataIp = vals["settings"]["network"]["dataIp"]
this.paramNetwork.dataMtu = vals["settings"]["network"]["dataMtu"]
this.paramNetwork.serverName = vals["settings"]["network"]["serverName"]
},
// ========== include end from 'common/all-params-methods.js.j2'
// ========== include from 'common/monitoring-methods.js.j2'
updateStatistics(vals) {
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
const modcods = [
"DUMMY",
"QPSK 1/4", "QPSK 1/3", "QPSK 2/5", "QPSK 1/2", "QPSK 3/5", "QPSK 2/3", "QPSK 3/4", "QPSK 4/5", "QPSK 5/6", "QPSK 8/9", "QPSK 9/10",
"8PSK 3/5", "8PSK 2/3", "8PSK 3/4", "8PSK 5/6", "8PSK 8/9", "8PSK 9/10",
"16APSK 2/3", "16APSK 3/4", "16APSK 4/5", "16APSK 5/6", "16APSK 8/9", "16APSK 9/10",
"32APSK 3/4", "32APSK 4/5", "32APSK 5/6", "32APSK 8/9", "32APSK 9/10",
]
if (typeof modcod != "number") {
return "?";
}
if (modcod < 0 || modcod >= modcods.length) {
return `? (${modcod})`
}
return modcods[modcod]
}
this.lastUpdateTime = new Date();
this.initState = vals["state"]["initState"]
this.testState = vals["state"]["testState"]
this.statRx.state = vals["state"]["rx"]["state"]
this.statRx.sym_sync_lock = vals["state"]["rx"]["sym_sync_lock"]
this.statRx.freq_search_lock = vals["state"]["rx"]["freq_search_lock"]
this.statRx.afc_lock = vals["state"]["rx"]["afc_lock"]
this.statRx.pkt_sync = vals["state"]["rx"]["pkt_sync"]
this.statRx.snr = Math.round(vals["state"]["rx"]["snr"] * 10) / 10
this.statRx.rssi = Math.round(vals["state"]["rx"]["rssi"] * 10) / 10
this.statRx.modcod = modcodToStr(vals["state"]["rx"]["modcod"])
this.statRx.frameSizeNormal = vals["state"]["rx"]["frameSizeNormal"]
this.statRx.isPilots = vals["state"]["rx"]["isPilots"]
this.statRx.symError = vals["state"]["rx"]["symError"]
this.statRx.freqErr = Math.round(vals["state"]["rx"]["freqErr"] * 100) / 100
this.statRx.freqErrAcc = Math.round(vals["state"]["rx"]["freqErrAcc"] * 100) / 100
this.statRx.inputSignalLevel = vals["state"]["rx"]["inputSignalLevel"]
this.statRx.pllError = Math.round(vals["state"]["rx"]["pllError"] * 100) / 100
this.statRx.speedOnRxKbit = Math.round(vals["state"]["rx"]["speedOnRxKbit"] * 100) / 100
this.statRx.speedOnIifKbit = Math.round(vals["state"]["rx"]["speedOnIifKbit"] * 100) / 100
this.statRx.packetsOk = vals["state"]["rx"]["packetsOk"]
this.statRx.packetsBad = vals["state"]["rx"]["packetsBad"]
this.statRx.packetsDummy = vals["state"]["rx"]["packetsDummy"]
this.statTx.state = vals["state"]["tx"]["state"]
this.statTx.modcod = modcodToStr(vals["state"]["tx"]["modcod"])
this.statTx.speedOnTxKbit = Math.round(vals["state"]["tx"]["speedOnTxKbit"] * 100) / 100
this.statTx.speedOnIifKbit = Math.round(vals["state"]["tx"]["speedOnIifKbit"] * 100) / 100
this.statTx.centerFreq = vals["state"]["tx"]["centerFreq"]
this.statTx.symSpeed = vals["state"]["tx"]["symSpeed"]
this.statDevice.adrv = vals["state"]["device"]["adrv"]
this.statDevice.zynq = vals["state"]["device"]["zynq"]
this.statDevice.fpga = vals["state"]["device"]["fpga"]
// аптайм приходит в секундах, надо преобразовать его в человеко-читаемый вид
let uptime = vals["state"]["device"]["uptime"]
if (uptime) {
let secs = uptime % 60; uptime = Math.floor(uptime / 60)
let mins = uptime % 60; uptime = Math.floor(uptime / 60)
let hours = uptime % 24
uptime = Math.floor( uptime / 24)
let res = `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
if (uptime > 0) { res = `${uptime} дней, ` + res }
this.statOs.uptime = res
} else {
this.statOs.uptime = '?'
}
this.statOs.load1 = Math.round(vals["state"]["device"]["load1min"] * 100) / 100
this.statOs.load5 = Math.round(vals["state"]["device"]["load5min"] * 100) / 100
this.statOs.load15 = Math.round(vals["state"]["device"]["load15min"] * 100) / 100
this.statOs.totalram = vals["state"]["device"]["totalram"]
this.statOs.freeram = vals["state"]["device"]["freeram"]
},
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
this.statRx.packetsDummy = 0
})
},
// ========== include end from 'common/monitoring-methods.js.j2'
// ========== include from 'common/setup-methods.js.j2'
// ========== include end from 'common/setup-methods.js.j2'
// ========== include from 'common/admin-methods.js.j2'
async settingsUploadUpdate() {
if (!this.uploadFw.filename) {
alert('Выберите файл для загрузки');
return;
}
async function readFileAsArrayBuffer(fileName) {
return new Promise((resolve, reject) => {
if (!fileName) { reject(`Файл не выбран`); return }
const reader = new FileReader();
reader.onload = (e) => { resolve(reader.result) }
reader.onerror = (e) => { reject(e) }
reader.readAsArrayBuffer(fileName)
})
}
try {
this.submitStatus.firmwareUpload = true
this.uploadFw.progress = 0
const blob = await readFileAsArrayBuffer(this.uploadFw.filename)
const xhr = new XMLHttpRequest();
await new Promise((resolve) => {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
this.uploadFw.progress = Math.round((event.loaded / event.total) * 1000) / 10;
}
});
xhr.addEventListener("loadend", () => {
this.uploadFw.progress = 100
const rep = JSON.parse(xhr.responseText);
this.uploadFw.sha256 = rep['sha256']
resolve(xhr.readyState === 4 && xhr.status === 200);
});
xhr.open("PUT", "/api/firmwareUpdate", true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(blob);
});
} catch (e) {
alert(`Ошибка загрузки файла: ${e}`);
}
this.submitStatus.firmwareUpload = false
},
async settingsPerformFirmwareUpgrade() {
if (this.submitStatus.firmwareUpgrade) { return }
this.submitStatus.firmwareUpgrade = true
try {
await fetch('/api/doFirmwareUpgrade', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgrade = false
},
doModemReboot() {
if (this.submitStatus.modemReboot !== null) {
return
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'dpdi', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'
performUpdateSettings() {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
let vals = await d.json()
this.settingFetchComplete = true
this.updateRxtxSettings(vals)
this.updateBuclnbSettings(vals)
this.updateNetworkSettings(vals)
if ('netServerName' in vals['settings']) {
document.getElementsByTagName('title')[0].innerText = vals['settings']['netServerName']
}
}
doFetchSettings().then(() => {})
}
},
mounted() {
const doFetchStatistics = async () => {
if (this.submitStatus.modemReboot !== null) {
this.initState = `Перезагрузка модема... Осталось ${this.submitStatus.modemReboot} сек`
this.submitStatus.modemReboot--
if (this.submitStatus.modemReboot <= 0) {
window.location.reload()
}
} else {
try {
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"
}
}
setTimeout(() => {
doFetchStatistics()
}, 1000)
}
const doFetchAbout = async () => {
try {
const fr = await fetch("/api/get/aboutFirmware")
const d = await fr.json()
this.about.firmwareVersion = d["firmware"]["version"]
this.about.modemUid = d["firmware"]["modemId"]
this.about.modemSn = d["firmware"]["modemSn"]
this.about.macManagement = d["firmware"]["macMang"]
this.about.macData = d["firmware"]["macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
});
app.mount('#app')
</script>
</body>
</html>

988
static/main-tdma.html Normal file
View File

@@ -0,0 +1,988 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VSAT Модем</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<link rel="stylesheet" type="text/css" href="/fields.css">
<style>
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: var(--bg-selected);
}
body { /* значение по-умолчанию */ --header-height: 60px; }
#content {
padding-top: var(--header-height);
}
.l3-proto-label {
margin: 0 0 0 0.5em;
}
.l3-proto-label > * {
display: inline-block;
}
.l3-proto-label input[type=checkbox] {
width: auto;
}
</style>
</head>
<body>
<div id="app" hidden>
<header>
<span class="nav-bar-element">Прием: <span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Передача: <span :class="{ indicator_good: statTx.state === true, indicator: true }"></span></span>
<span class="nav-bar-element">Тест: <span :class="{ indicator_good: testState, indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
<span :class="{ value_bad: initState !== 'Успешная инициализация системы' }">{{ initState }}</span>
<div class="tabs-header">
<span style="font-weight:bold">VSAT Модем</span>
<a href="#monitoring" class="tabs-btn" @click="activeTab = 'monitoring'" :class="{ active: activeTab === 'monitoring' }">Мониторинг</a>
<a href="#setup" class="tabs-btn" @click="activeTab = 'setup'" :class="{ active: activeTab === 'setup' }">Настройки</a>
<a href="#admin" class="tabs-btn" @click="activeTab = 'admin'" :class="{ active: activeTab === 'admin' }">Администрирование</a>
<a href="/logout" class="tabs-btn">Выход</a>
</div>
</header>
<div id="content">
<div class="tabs-body-item tabs-item-flex-container" v-if="activeTab === 'monitoring'">
<div class="settings-set-container statistics-container">
<h2>Статистика приема</h2>
<table>
<tbody>
<tr><th>Прием</th><td><span :class="{ indicator_bad: statRx.state === false, indicator_good: statRx.state === true, indicator: true }"></span></td></tr>
<tr><th>Захват символьной</th><td><span :class="{ indicator_bad: statRx.sym_sync_lock === false, indicator_good: statRx.sym_sync_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват ФАПЧ</th><td><span :class="{ indicator_bad: statRx.afc_lock === false, indicator_good: statRx.afc_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват поиска по частоте</th><td><span :class="{ indicator_bad: statRx.freq_search_lock === false, indicator_good: statRx.freq_search_lock === true, indicator: true }"></span></td></tr>
<tr><th>Захват пакетной синхр.</th><td><span :class="{ indicator_bad: statRx.pkt_sync === false, indicator_good: statRx.pkt_sync === true, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ statRx.snr }} / {{ statRx.rssi }}</td></tr>
<tr><th>Modcod</th><td>{{ statRx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ statRx.frameSizeNormal ? 'normal' : 'short' }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ statRx.isPilots ? 'pilots' : 'no pilots' }}</td></tr>
<tr><th>Символьная ошибка</th><td>{{ statRx.symError }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ statRx.freqErr }} / {{ statRx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ statRx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ statRx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ statRx.speedOnRxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statRx.speedOnIifKbit }} кбит/с</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статистика пакетов</td></tr>
<tr><th>Качественных пакетов</th><td>{{ statRx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ statRx.packetsBad }}</td></tr>
<tr><th>DUMMY</th><td>{{ statRx.packetsDummy }}</td></tr>
</tbody>
</table>
<button class="action-button" @click="resetPacketsStatistics()"> Сброс статистики </button>
</div>
<div class="settings-set-container statistics-container">
<h2>Статистика передачи</h2>
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: statTx.state === false, indicator_good: statTx.state === true, indicator: true }"></span></td></tr>
<tr><th>Modcod</th><td>{{ statTx.modcod }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ statTx.speedOnTxKbit }} кбит/с</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ statTx.speedOnIifKbit }} кбит/с</td></tr>
<tr><th>Центральная частота</th><td>{{ statTx.centerFreq }} кГц</td></tr>
<tr><th>Символьная скорость</th><td>{{ statTx.symSpeed }} ksymb</td></tr>
</tbody>
</table>
</div>
<div class="settings-set-container statistics-container">
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ statDevice.adrv }} °C</td></tr>
<tr><th>Температура ZYNQ</th><td>{{ statDevice.zynq }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ statDevice.fpga }} °C</td></tr>
<tr><th>Время работы устройства</th><td>{{ statOs.uptime }}</td></tr>
<tr><th>Средняя загрузка ЦП (1/5/15 мин.)</th><td>{{ statOs.load1 }}% {{ statOs.load5 }}% {{ statOs.load15 }}%</td></tr>
<tr><th>ОЗУ всего/свободно</th><td>{{ statOs.totalram }}МБ/{{ statOs.freeram }}МБ</td></tr>
<tr><td colspan="2" style="padding-top: 1em; text-align: center">Статус обновления</td></tr>
<tr><th>Статус</th><td>{{ statDevice.upgradeStatus }}</td></tr>
<tr><th>Прогресс</th><td>{{ statDevice.upgradePercent }}%</td></tr>
<tr><th>Имя образа</th><td><code>{{ statDevice.upgradeImage }}</code></td></tr>
</tbody>
</table>
</div>
</div>
<div class="tabs-body-item" v-if="activeTab === 'setup' && settingFetchComplete">
<h2>Настройки приема/передачи</h2>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки передатчика</h3>
<label>
<span>Включить передатчик</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.txEn" /><span class="slider"></span></span>
</label>
<label>
<span>Режим работы модулятора</span>
<select v-model="paramRxtx.txModulatorIsTest">
<option :value="false">Нормальный</option>
<option :value="true">Тест (CW)</option>
</select>
</label>
<label v-show="paramRxtx.txModulatorIsTest">
<span>Центральная частота, КГц</span>
<input type="text" v-model.lazy="paramRxtx.txCentralFreq" @change="e => paramRxtx.txCentralFreq = inputFormatNumber(inputFormatNumber(e.target.value, {min:900000,step:0.01}), {min:900000,step:0.01})"/>
</label>
<label><span>Ослабление, дБ</span><input type="number" v-model="paramRxtx.txAttenuation" min="-90" step="1"/></label>
<label><span>Ограничение ослабления</span><input type="number" v-model="paramRxtx.txAttenuationLimit" min="-40" step="0.25"/></label>
</div>
<div class="settings-set-container">
<h3>Настройки приемника</h3>
<label>
<span>Режим управления усилением</span>
<select v-model="paramRxtx.rxAgcEn">
<option :value="true">АРУ</option>
<option :value="false">РРУ</option>
</select>
</label>
<label v-show="!paramRxtx.rxAgcEn"><span>Ручное усиление, дБ</span><input type="number" v-model="paramRxtx.rxManualGain" min="-40"/></label>
<label>
<span>Инверсия спектра</span>
<span class="toggle-input"><input type="checkbox" v-model="paramRxtx.rxSpectrumInversion" /><span class="slider"></span></span>
</label>
<label>
<span>Центральная частота, КГц</span>
<input type="text" v-model.lazy="paramRxtx.rxCentralFreq" @change="e => paramRxtx.rxCentralFreq = inputFormatNumber(inputFormatNumber(e.target.value, {min:900000,step:0.01}), {min:900000,step:0.01})"/>
</label>
<label>
<span>Символьная скорость, Бод</span>
<input type="text" v-model.lazy="paramRxtx.rxBaudrate" @change="e => paramRxtx.rxBaudrate = inputFormatNumber(inputFormatNumber(e.target.value, {min:200000,max:54000000,step:1}), {min:200000,max:54000000,step:1})"/>
</label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.rxRolloff">
<option :value="20">0.02</option>
<option :value="50">0.05</option>
<option :value="100">0.10</option>
<option :value="150">0.15</option>
<option :value="200">0.20</option>
<option :value="250">0.25</option>
</select>
</label>
</div>
</div>
<button class="action-button" @click="settingsSubmitRxtx()">Сохранить <span class="submit-spinner" v-show="submitStatus.rxtx"></span></button>
<h2>Настройки DPDI</h2>
<div class="settings-set-container">
<label>
<span>Метод расчета задержки</span>
<select v-model="paramDpdi.isPositional">
<option :value="true">Позиционированием</option>
<option :value="false">Окном задержки</option>
</select>
</label>
<h2 v-show="paramDpdi.isPositional === true">Настройки позиционирования</h2>
<label v-show="paramDpdi.isPositional === true"><span>Широта станции</span><input type="number" v-model="paramDpdi.positionStationLatitude" min="-180" max="180" step="1e-06"/></label>
<label v-show="paramDpdi.isPositional === true"><span>Долгота станции</span><input type="number" v-model="paramDpdi.positionStationLongitude" min="-180" max="180" step="1e-06"/></label>
<label v-show="paramDpdi.isPositional === true"><span>Подспутниковая точка</span><input type="number" v-model="paramDpdi.positionSatelliteLongitude" min="-180" max="180" step="1e-06"/></label>
<label v-show="paramDpdi.isPositional === false"><span>Задержка до спутника, мс</span><input type="number" v-model="paramDpdi.delay" max="400" step="0.1"/></label>
</div>
<button class="action-button" @click="settingsSubmitDpdi()">Сохранить <span class="submit-spinner" v-show="submitStatus.dpdi"></span></button>
<h2>Настройки питания и опорного генератора</h2>
<div class="tabs-item-flex-container">
<div class="settings-set-container">
<h3>Настройки BUC</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.bucRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание BUC</span>
<select v-model="paramBuclnb.bucPowering">
<option :value="0">Выкл</option>
<option :value="24">24В</option>
</select>
</label>
<label>
<span>Частота LO, кГц</span>
<input type="text" v-model.lazy="paramBuclnb.bucLoKhz" @change="e => paramBuclnb.bucLoKhz = inputFormatNumber(inputFormatNumber(e.target.value, {max:40000000,step:1}), {max:40000000,step:1})"/>
</label>
</div>
<div class="settings-set-container">
<h3>Настройки LNB</h3>
<label>
<span>Подача опоры 10МГц</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.lnbRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Питание LNB</span>
<select v-model="paramBuclnb.lnbPowering">
<option :value="0">Выкл</option>
<option :value="13">13В</option>
<option :value="18">18В</option>
<option :value="24">24В</option>
</select>
</label>
<label>
<span>Частота LO, кГц</span>
<input type="text" v-model.lazy="paramBuclnb.lnbLoKhz" @change="e => paramBuclnb.lnbLoKhz = inputFormatNumber(inputFormatNumber(e.target.value, {max:40000000,step:1}), {max:40000000,step:1})"/>
</label>
</div>
<div class="settings-set-container">
<h3>Сервисные настройки</h3>
<label>
<span>Подача опоры 10МГц на 'Выход 10МГц'</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.srvRefClk10M" /><span class="slider"></span></span>
</label>
<label>
<span>Автозапуск BUC и LNB при включении</span>
<span class="toggle-input"><input type="checkbox" v-model="paramBuclnb.bucLnbAutoStart" /><span class="slider"></span></span>
</label>
</div>
</div>
<button class="action-button" @click="settingsSubmitBuclnb()">Сохранить <span class="submit-spinner" v-show="submitStatus.buclnb"></span></button>
</div> <div class="tabs-body-item" v-if="activeTab === 'admin' && settingFetchComplete">
<h2>Настройки сети</h2>
<div class="settings-set-container">
<h3>Настройки интерфейса управления</h3>
<label>
<span>Интерфейс управления (a.d.d.r/mask)</span>
<input v-model="paramNetwork.managementIp" required type="text" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$">
</label>
<label>
<span>Имя веб-сервера</span>
<input v-model="paramNetwork.serverName" type="text">
</label>
</div>
<button class="action-button" @click="settingsSubmitNetwork()">Сохранить <span class="submit-spinner" v-show="submitStatus.network"></span></button>
<h2>Система</h2>
<div class="settings-set-container statistics-container">
<table>
<tbody>
<tr><th>Версия ПО</th><td>{{ about.firmwareVersion }}</td></tr>
<tr><th>ID модема</th><td>{{ about.modemUid }}</td></tr>
<tr><th>Серийный номер</th><td>{{ about.modemSn }}</td></tr>
<tr><th>MAC интерфейса управления</th><td>{{ about.macManagement }}</td></tr>
<tr><th>MAC интерфейса данных</th><td>{{ about.macData }}</td></tr>
</tbody>
</table>
<div>
<button class="dangerous-button" @click="doModemReboot()">Перезагрузить модем <span class="submit-spinner" v-show="submitStatus.modemReboot !== null"></span></button>
</div>
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Вход в сеть ЦЗС</h2>
<div class="settings-set-container statistics-container">
<label>
<span>Хеш-строка пароля (выдается оператором NMS)</span>
<input v-model="cesPasswordValue" type="text">
</label>
<button class="action-button" @click="settingsPerformSetCesPassword()">Установить пароль<span class="submit-spinner" v-show="submitStatus.cesPassword"></span></button>
</div>
<h2>Обновление ПО</h2>
<div class="settings-set-container statistics-container">
<h3>Ручное обновление</h3>
<label>
<span>Файл {{ this.uploadFw.progress !== null ? `(${this.uploadFw.progress}%)` : '' }}</span>
<input type="file" accept="application/zip" @change="(e) => { this.uploadFw.filename = e.target.files[0] }">
<span v-if="uploadFw.sha256 !== null">SHA256: {{ uploadFw.sha256 }}</span>
</label>
<button class="action-button" @click="settingsUploadUpdate()">Загрузить<span class="submit-spinner" v-show="submitStatus.firmwareUpload"></span></button>
<button class="dangerous-button" v-show="uploadFw.sha256 !== null" @click="settingsPerformFirmwareUpgrade()">Обновить встроенное ПО<span class="submit-spinner" v-show="submitStatus.firmwareUpgrade"></span></button>
<h3 v-show="statDevice.upgradePercent >= 100">Обновление "по воздуху"</h3>
<button class="dangerous-button" v-show="statDevice.upgradePercent >= 100" @click="settingsPerformFirmwareUpgradeOta()">Обновить встроенное ПО<span class="submit-spinner" v-show="submitStatus.firmwareUpgradeOta"></span></button>
</div>
</div>
<p>Последнее обновление статистики: {{ lastUpdateTime }}</p>
</div>
</div>
<script src="/js/vue.js?v=3.5.13"></script>
<script>
const availableTabs = ['monitoring', 'setup', 'admin']
// для обновления высоты хидера
function updateHeaderHeight() { const header = document.querySelector('header'); document.body.style.setProperty('--header-height', `${header.offsetHeight}px`); }
window.addEventListener('load', updateHeaderHeight); window.addEventListener('resize', updateHeaderHeight);
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return availableTabs[0]
}
function toLocaleStringWithSpaces(num) {
if (typeof num !== 'number') {
if (typeof num === 'string') { return num }
return String(num);
}
const numberString = num.toString()
const [integerPart, fractionalPart] = numberString.split('.')
const spacedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
if (fractionalPart) { return `${spacedIntegerPart}.${fractionalPart}` }
else { return spacedIntegerPart }
}
const app = Vue.createApp({
data() {
return {
// false - означает что статистика не отправляется, true - отправляется
submitStatus: {
rxtx: false,
buclnb: false,
dpdi: false,
network: false,
firmwareUpload: false,
firmwareUpgrade: false,
firmwareUpgradeOta: false,
cesPassword: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
cesPasswordValue: '',
// ========== include from 'common/all-params-data.js.j2'
paramRxtx: {
txEn: false,
txModulatorIsTest: false,
txCentralFreq: 0,
txAttenuation: -90,
txAttenuationLimit: -40,
rxAgcEn: true,
rxManualGain: -40,
rxSpectrumInversion: false,
rxCentralFreq: 0,
rxBaudrate: 0,
rxRolloff: 20,
},
paramBuclnb: {
bucRefClk10M: false,
bucPowering: 0,
bucLoKhz: 0,
lnbRefClk10M: false,
lnbPowering: 0,
lnbLoKhz: 0,
srvRefClk10M: false,
bucLnbAutoStart: false,
},
paramDpdi: {
isPositional: true,
positionStationLatitude: -180,
positionStationLongitude: -180,
positionSatelliteLongitude: -180,
delay: 0,
},
paramNetwork: {
managementIp: null,
serverName: null,
},
// ========== include end from 'common/all-params-data.js.j2'
// ========== include from 'common/monitoring-data.js.j2'
statRx: {
// индикаторы
state: '?', // общее состояние
sym_sync_lock: '?', // захват символьной
freq_search_lock: '?', // Захват поиска по частоте
afc_lock: '?', // захват ФАПЧ
pkt_sync: '?', // захват пакетной синхронизации
// куча других параметров, идет в том же порядке, что и в таблице
snr: '?', rssi: '?',
modcod: '?', frameSizeNormal: '?',
isPilots: '?',
symError: '?',
freqErr: '?', freqErrAcc: '?',
inputSignalLevel: '?',
pllError: '?',
speedOnRxKbit: '?',
speedOnIifKbit: '?',
// статистика пакетов
packetsOk: '?', packetsBad: '?', packetsDummy: '?',
},
statTx: {
// состояние
state: '?',
// прочие поля
modcod: '?', speedOnTxKbit: '?', speedOnIifKbit: '?', centerFreq: '?', symSpeed: '?'
},
statDevice: { // температурные датчики
adrv: 0, zynq: 0, fpga: 0
,
upgradeStatus: "", upgradePercent: 0, upgradeImage: ""
},
statOs: {uptime: '?', load1: '?', load5: '?', load15: '?', totalram: '?', freeram: '?'},
// ========== include end from 'common/monitoring-data.js.j2'
// ========== include from 'common/setup-data.js.j2'
// ========== include end from 'common/setup-data.js.j2'
// ========== include from 'common/admin-data.js.j2'
// ========== include end from 'common/admin-data.js.j2'
uploadFw: {
progress: null,
filename: null,
sha256: null
},
// эти "настройки" - read only
about: {
firmwareVersion: '?',
modemUid: '?',
modemSn: '?',
macManagement: '?',
macData: '?',
},
testState: false,
initState: '',
lastUpdateTime: new Date(),
activeTab: getCurrentTab(),
settingFetchComplete: false,
}
},
methods: {
correctModcodSpeed(modulation, speed) {
const mod = modulation.toLowerCase()
const available = {
"qpsk": ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"8psk": ['2/3', '3/4', '5/6', '8/9', '9/10'],
"16apsk": ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10'],
"32apsk": ['3/4', '4/5', '5/6', '8/9', '9/10']
}
if (mod in available) {
if (available[mod].indexOf(speed) >= 0) {
return speed
}
return available[mod][0]
}
return ""
},
getAvailableModcods(modulation) {
// NOTE модкоды со скоростью хода 3/5 не работают
switch (modulation) {
case 'qpsk':
return ['1/4', '1/3', '2/5', '1/2', '3/5', '2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '8psk':
return ['3/5', '2/3', '3/4', '5/6', '8/9', '9/10']
case '16apsk':
return ['2/3', '3/4', '4/5', '5/6', '8/9', '9/10']
case '32apsk':
return ['3/4', '4/5', '5/6', '8/9', '9/10']
default:
return []
}
},
inputFormatNumber(src, validation) {
if (validation === null || validation === undefined) { validation = {} }
const rawVal = src.toString().replace(/[^0-9.,]/g, '').replace(',', '.')
let result = rawVal === '' ? 0 : parseFloat(rawVal)
const step = 'step' in validation ? validation['step']: 1.0
result = Math.round(result / step) * step
if ('min' in validation) { if (result <= validation['min']) { result = validation['min'] } }
if ('max' in validation) { if (result >= validation['max']) { result = validation['max'] } }
return toLocaleStringWithSpaces(result)
},
// ========== include from 'common/all-params-methods.js.j2'
settingsSubmitRxtx() {
if (this.submitStatus.rxtx) { return }
let query = {
"txEn": this.paramRxtx.txEn,
"txModulatorIsTest": this.paramRxtx.txModulatorIsTest,
"txCentralFreq": parseFloat(this.paramRxtx.txCentralFreq.replace(/[^0-9,.]/g, '').replace(',', '.')),
"txAttenuation": this.paramRxtx.txAttenuation,
"txAttenuationLimit": this.paramRxtx.txAttenuationLimit,
"rxAgcEn": this.paramRxtx.rxAgcEn,
"rxManualGain": this.paramRxtx.rxManualGain,
"rxSpectrumInversion": this.paramRxtx.rxSpectrumInversion,
"rxCentralFreq": parseFloat(this.paramRxtx.rxCentralFreq.replace(/[^0-9,.]/g, '').replace(',', '.')),
"rxBaudrate": parseFloat(this.paramRxtx.rxBaudrate.replace(/[^0-9,.]/g, '').replace(',', '.')),
"rxRolloff": this.paramRxtx.rxRolloff,
}
this.submitStatus.rxtx = true
fetch('/api/set/rxtx', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateRxtxSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.rxtx = false })
},
settingsSubmitBuclnb() {
if (this.submitStatus.buclnb) { return }
{ if (!confirm("Применение неправильных настроек может вывести из строя оборудование! Продолжить?")) return }
let query = {
"bucRefClk10M": this.paramBuclnb.bucRefClk10M,
"bucPowering": this.paramBuclnb.bucPowering,
"bucLoKhz": parseFloat(this.paramBuclnb.bucLoKhz.replace(/[^0-9,.]/g, '').replace(',', '.')),
"lnbRefClk10M": this.paramBuclnb.lnbRefClk10M,
"lnbPowering": this.paramBuclnb.lnbPowering,
"lnbLoKhz": parseFloat(this.paramBuclnb.lnbLoKhz.replace(/[^0-9,.]/g, '').replace(',', '.')),
"srvRefClk10M": this.paramBuclnb.srvRefClk10M,
"bucLnbAutoStart": this.paramBuclnb.bucLnbAutoStart,
}
this.submitStatus.buclnb = true
fetch('/api/set/buclnb', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateBuclnbSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.buclnb = false })
},
settingsSubmitDpdi() {
if (this.submitStatus.dpdi) { return }
let query = {
"isPositional": this.paramDpdi.isPositional,
"positionStationLatitude": this.paramDpdi.positionStationLatitude,
"positionStationLongitude": this.paramDpdi.positionStationLongitude,
"positionSatelliteLongitude": this.paramDpdi.positionSatelliteLongitude,
"delay": this.paramDpdi.delay,
}
this.submitStatus.dpdi = true
fetch('/api/set/dpdi', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateDpdiSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.dpdi = false })
},
settingsSubmitNetwork() {
if (this.submitStatus.network) { return }
{ if (!confirm("Применение этих настроек может сделать модем недоступным! Продолжить?")) return }
let query = {
"managementIp": this.paramNetwork.managementIp,
"serverName": this.paramNetwork.serverName,
}
this.submitStatus.network = true
fetch('/api/set/network', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(query), credentials: 'same-origin' })
.then(async (resp) => { let vals = await resp.json(); if (vals['status'] !== 'ok') { throw new Error(vals['error'] ? vals['error'] : "Server returns undefined error") } this.updateNetworkSettings(vals) })
.catch((reason) => { alert(`Ошибка при применении настроек: ${reason}`) })
.finally(() => { this.submitStatus.network = false })
},
updateRxtxSettings(vals) {
this.submitStatus.rxtx = false
this.paramRxtx.txEn = vals["settings"]["rxtx"]["txEn"]
this.paramRxtx.txModulatorIsTest = vals["settings"]["rxtx"]["txModulatorIsTest"]
this.paramRxtx.txCentralFreq = this.inputFormatNumber(vals["settings"]["rxtx"]["txCentralFreq"], {min:900000,step:0.01})
this.paramRxtx.txAttenuation = vals["settings"]["rxtx"]["txAttenuation"]
this.paramRxtx.txAttenuationLimit = vals["settings"]["rxtx"]["txAttenuationLimit"]
this.paramRxtx.rxAgcEn = vals["settings"]["rxtx"]["rxAgcEn"]
this.paramRxtx.rxManualGain = vals["settings"]["rxtx"]["rxManualGain"]
this.paramRxtx.rxSpectrumInversion = vals["settings"]["rxtx"]["rxSpectrumInversion"]
this.paramRxtx.rxCentralFreq = this.inputFormatNumber(vals["settings"]["rxtx"]["rxCentralFreq"], {min:900000,step:0.01})
this.paramRxtx.rxBaudrate = this.inputFormatNumber(vals["settings"]["rxtx"]["rxBaudrate"], {min:200000,max:54000000,step:1})
this.paramRxtx.rxRolloff = vals["settings"]["rxtx"]["rxRolloff"]
},
updateBuclnbSettings(vals) {
this.submitStatus.buclnb = false
this.paramBuclnb.bucRefClk10M = vals["settings"]["buclnb"]["bucRefClk10M"]
this.paramBuclnb.bucPowering = vals["settings"]["buclnb"]["bucPowering"]
this.paramBuclnb.bucLoKhz = this.inputFormatNumber(vals["settings"]["buclnb"]["bucLoKhz"], {max:40000000,step:1})
this.paramBuclnb.lnbRefClk10M = vals["settings"]["buclnb"]["lnbRefClk10M"]
this.paramBuclnb.lnbPowering = vals["settings"]["buclnb"]["lnbPowering"]
this.paramBuclnb.lnbLoKhz = this.inputFormatNumber(vals["settings"]["buclnb"]["lnbLoKhz"], {max:40000000,step:1})
this.paramBuclnb.srvRefClk10M = vals["settings"]["buclnb"]["srvRefClk10M"]
this.paramBuclnb.bucLnbAutoStart = vals["settings"]["buclnb"]["bucLnbAutoStart"]
},
updateDpdiSettings(vals) {
this.submitStatus.dpdi = false
this.paramDpdi.isPositional = vals["settings"]["dpdi"]["isPositional"]
this.paramDpdi.positionStationLatitude = vals["settings"]["dpdi"]["positionStationLatitude"]
this.paramDpdi.positionStationLongitude = vals["settings"]["dpdi"]["positionStationLongitude"]
this.paramDpdi.positionSatelliteLongitude = vals["settings"]["dpdi"]["positionSatelliteLongitude"]
this.paramDpdi.delay = vals["settings"]["dpdi"]["delay"]
},
updateNetworkSettings(vals) {
this.submitStatus.network = false
this.paramNetwork.managementIp = vals["settings"]["network"]["managementIp"]
this.paramNetwork.serverName = vals["settings"]["network"]["serverName"]
},
// ========== include end from 'common/all-params-methods.js.j2'
// ========== include from 'common/monitoring-methods.js.j2'
updateStatistics(vals) {
function modcodToStr(modcod) {
// модкоды из раздела 5.5.2.2 https://www.etsi.org/deliver/etsi_en/302300_302399/302307/01.01.02_60/en_302307v010102p.pdf
const modcods = [
"DUMMY",
"QPSK 1/4", "QPSK 1/3", "QPSK 2/5", "QPSK 1/2", "QPSK 3/5", "QPSK 2/3", "QPSK 3/4", "QPSK 4/5", "QPSK 5/6", "QPSK 8/9", "QPSK 9/10",
"8PSK 3/5", "8PSK 2/3", "8PSK 3/4", "8PSK 5/6", "8PSK 8/9", "8PSK 9/10",
"16APSK 2/3", "16APSK 3/4", "16APSK 4/5", "16APSK 5/6", "16APSK 8/9", "16APSK 9/10",
"32APSK 3/4", "32APSK 4/5", "32APSK 5/6", "32APSK 8/9", "32APSK 9/10",
]
if (typeof modcod != "number") {
return "?";
}
if (modcod < 0 || modcod >= modcods.length) {
return `? (${modcod})`
}
return modcods[modcod]
}
this.lastUpdateTime = new Date();
this.initState = vals["state"]["initState"]
this.testState = vals["state"]["testState"]
this.statRx.state = vals["state"]["rx"]["state"]
this.statRx.sym_sync_lock = vals["state"]["rx"]["sym_sync_lock"]
this.statRx.freq_search_lock = vals["state"]["rx"]["freq_search_lock"]
this.statRx.afc_lock = vals["state"]["rx"]["afc_lock"]
this.statRx.pkt_sync = vals["state"]["rx"]["pkt_sync"]
this.statRx.snr = Math.round(vals["state"]["rx"]["snr"] * 10) / 10
this.statRx.rssi = Math.round(vals["state"]["rx"]["rssi"] * 10) / 10
this.statRx.modcod = modcodToStr(vals["state"]["rx"]["modcod"])
this.statRx.frameSizeNormal = vals["state"]["rx"]["frameSizeNormal"]
this.statRx.isPilots = vals["state"]["rx"]["isPilots"]
this.statRx.symError = vals["state"]["rx"]["symError"]
this.statRx.freqErr = Math.round(vals["state"]["rx"]["freqErr"] * 100) / 100
this.statRx.freqErrAcc = Math.round(vals["state"]["rx"]["freqErrAcc"] * 100) / 100
this.statRx.inputSignalLevel = vals["state"]["rx"]["inputSignalLevel"]
this.statRx.pllError = Math.round(vals["state"]["rx"]["pllError"] * 100) / 100
this.statRx.speedOnRxKbit = Math.round(vals["state"]["rx"]["speedOnRxKbit"] * 100) / 100
this.statRx.speedOnIifKbit = Math.round(vals["state"]["rx"]["speedOnIifKbit"] * 100) / 100
this.statRx.packetsOk = vals["state"]["rx"]["packetsOk"]
this.statRx.packetsBad = vals["state"]["rx"]["packetsBad"]
this.statRx.packetsDummy = vals["state"]["rx"]["packetsDummy"]
this.statTx.state = vals["state"]["tx"]["state"]
this.statTx.modcod = modcodToStr(vals["state"]["tx"]["modcod"])
this.statTx.speedOnTxKbit = Math.round(vals["state"]["tx"]["speedOnTxKbit"] * 100) / 100
this.statTx.speedOnIifKbit = Math.round(vals["state"]["tx"]["speedOnIifKbit"] * 100) / 100
this.statTx.centerFreq = vals["state"]["tx"]["centerFreq"]
this.statTx.symSpeed = vals["state"]["tx"]["symSpeed"]
this.statDevice.adrv = vals["state"]["device"]["adrv"]
this.statDevice.zynq = vals["state"]["device"]["zynq"]
this.statDevice.fpga = vals["state"]["device"]["fpga"]
this.statDevice.upgradeStatus = vals["state"]["device"]["upgradeStatus"]
this.statDevice.upgradePercent = vals["state"]["device"]["upgradePercent"]
this.statDevice.upgradeImage = vals["state"]["device"]["upgradeImage"]
// аптайм приходит в секундах, надо преобразовать его в человеко-читаемый вид
let uptime = vals["state"]["device"]["uptime"]
if (uptime) {
let secs = uptime % 60; uptime = Math.floor(uptime / 60)
let mins = uptime % 60; uptime = Math.floor(uptime / 60)
let hours = uptime % 24
uptime = Math.floor( uptime / 24)
let res = `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
if (uptime > 0) { res = `${uptime} дней, ` + res }
this.statOs.uptime = res
} else {
this.statOs.uptime = '?'
}
this.statOs.load1 = Math.round(vals["state"]["device"]["load1min"] * 100) / 100
this.statOs.load5 = Math.round(vals["state"]["device"]["load5min"] * 100) / 100
this.statOs.load15 = Math.round(vals["state"]["device"]["load15min"] * 100) / 100
this.statOs.totalram = vals["state"]["device"]["totalram"]
this.statOs.freeram = vals["state"]["device"]["freeram"]
},
resetPacketsStatistics() {
fetch('/api/resetPacketStatistics', {
method: 'POST', credentials: 'same-origin'
}).then(() => {
this.statRx.packetsOk = 0
this.statRx.packetsBad = 0
this.statRx.packetsDummy = 0
})
},
// ========== include end from 'common/monitoring-methods.js.j2'
// ========== include from 'common/setup-methods.js.j2'
// ========== include end from 'common/setup-methods.js.j2'
// ========== include from 'common/admin-methods.js.j2'
async settingsUploadUpdate() {
if (!this.uploadFw.filename) {
alert('Выберите файл для загрузки');
return;
}
async function readFileAsArrayBuffer(fileName) {
return new Promise((resolve, reject) => {
if (!fileName) { reject(`Файл не выбран`); return }
const reader = new FileReader();
reader.onload = (e) => { resolve(reader.result) }
reader.onerror = (e) => { reject(e) }
reader.readAsArrayBuffer(fileName)
})
}
try {
this.submitStatus.firmwareUpload = true
this.uploadFw.progress = 0
const blob = await readFileAsArrayBuffer(this.uploadFw.filename)
const xhr = new XMLHttpRequest();
await new Promise((resolve) => {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
this.uploadFw.progress = Math.round((event.loaded / event.total) * 1000) / 10;
}
});
xhr.addEventListener("loadend", () => {
this.uploadFw.progress = 100
const rep = JSON.parse(xhr.responseText);
this.uploadFw.sha256 = rep['sha256']
resolve(xhr.readyState === 4 && xhr.status === 200);
});
xhr.open("PUT", "/api/firmwareUpdate", true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(blob);
});
} catch (e) {
alert(`Ошибка загрузки файла: ${e}`);
}
this.submitStatus.firmwareUpload = false
},
async settingsPerformFirmwareUpgrade() {
if (this.submitStatus.firmwareUpgrade) { return }
this.submitStatus.firmwareUpgrade = true
try {
await fetch('/api/doFirmwareUpgrade', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgrade = false
},
async settingsPerformFirmwareUpgradeOta() {
if (this.submitStatus.firmwareUpgradeOta) { return }
this.submitStatus.firmwareUpgradeOta = true
try {
await fetch('/api/doFirmwareUpgrade?ota=1', { method: 'POST' })
} catch (e) {
console.log("failed to perform upgrade firmware: ", e)
}
this.submitStatus.firmwareUpgradeOta = false
},
async settingsPerformSetCesPassword() {
if (this.submitStatus.cesPassword) { return }
this.submitStatus.cesPassword = true
try {
await fetch('/api/set/cesPassword', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'password': this.cesPasswordValue})
})
} catch (e) {
console.log("failed to perform set CES password: ", e)
}
this.submitStatus.cesPassword = false
},
doModemReboot() {
if (this.submitStatus.modemReboot !== null) {
return
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'dpdi', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'
performUpdateSettings() {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
let vals = await d.json()
this.settingFetchComplete = true
this.updateRxtxSettings(vals)
this.updateBuclnbSettings(vals)
this.updateDpdiSettings(vals)
this.updateNetworkSettings(vals)
if ('netServerName' in vals['settings']) {
document.getElementsByTagName('title')[0].innerText = vals['settings']['netServerName']
}
}
doFetchSettings().then(() => {})
}
},
mounted() {
const doFetchStatistics = async () => {
if (this.submitStatus.modemReboot !== null) {
this.initState = `Перезагрузка модема... Осталось ${this.submitStatus.modemReboot} сек`
this.submitStatus.modemReboot--
if (this.submitStatus.modemReboot <= 0) {
window.location.reload()
}
} else {
try {
let d = await fetch("/api/get/statistics", { credentials: 'same-origin' })
this.updateStatistics(await d.json())
} catch (e) {
this.initState = "Ошибка обновления статистики"
}
}
setTimeout(() => {
doFetchStatistics()
}, 1000)
}
const doFetchAbout = async () => {
try {
const fr = await fetch("/api/get/aboutFirmware")
const d = await fr.json()
this.about.firmwareVersion = d["firmware"]["version"]
this.about.modemUid = d["firmware"]["modemId"]
this.about.modemSn = d["firmware"]["modemSn"]
this.about.macManagement = d["firmware"]["macMang"]
this.about.macData = d["firmware"]["macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
});
app.mount('#app')
</script>
</body>
</html>

View File

@@ -1,331 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSCM-101</title>
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
.tabs-header {
margin: 0.5em 0;
background: var(--brand-bg);
}
.tabs-header > * {
display: inline-block;
}
.tabs-btn {
text-decoration: none;
font-size: 18px;
border: none;
padding: 10px 25px;
text-align: center;
cursor: pointer;
margin-bottom: -3px;
border-bottom: 3px solid #eee;
}
.tabs-btn.active {
color: var(--brand-text);
border-bottom: 3px solid var(--brand-text);
}
.tabs-body-item {
padding: 20px 0;
}
.indicator_bad {
background: var(--text-bad);
}
.indicator_good {
background: var(--text-good);
}
.indicator {
display: inline-block;
width: 1em;
height: 1em;
border: solid 1px var(--text-color2);
border-radius: 0.5em;
}
.state-bar-element {
margin: 0.5em;
border-bottom: 2px solid var(--text-color2);
}
.tabs-item-flex-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.tabs-item-flex-container > div {
padding: 1em;
margin: 1em;
border: 1px solid var(--text-color2);
border-radius: 0.2em;
}
.tabs-item-flex-container th {
text-align: left;
}
.tabs-item-flex-container h2 {
margin-top: 0;
}
</style>
</head>
<body>
<div id="app" hidden>
<div>
<span class="state-bar-element">Прием: <span :class="{ indicator_bad: stat_rx.state === true, indicator_good: stat_rx.state === false, indicator: true }"></span></span>
<span class="state-bar-element">Передача: <span :class="{ indicator_bad: stat_tx.state === true, indicator_good: stat_tx.state === false, indicator: true }"></span></span>
<span class="state-bar-element">Тест: <span :class="{ indicator_bad: testState === true, indicator_good: testState === false, indicator: true }"></span></span>
<!-- Последнее обновление: {{ lastUpdateTime }}-->
</div>
<div class="tabs">
<div class="tabs-header">
<span style="font-weight:bold">RSCM-101</span>
<a href="#monitoring" class="tabs-btn" @click="activeTab = 'monitoring'" :class="{ active: activeTab === 'monitoring' }">Мониторинг</a>
<a href="#setup" class="tabs-btn" @click="activeTab = 'setup'" :class="{ active: activeTab === 'setup' }">Настройки</a>
<a href="#admin" class="tabs-btn" @click="activeTab = 'admin'" :class="{ active: activeTab === 'admin' }">Администрирование</a>
</div>
<div class="tabs-body">
<div class="tabs-body-item tabs-item-flex-container" v-show="activeTab === 'monitoring'">
<div>
<h2>Статистика приема</h2>
<table>
<tbody>
<tr><th>Прием</th><td><span :class="{ indicator_bad: stat_rx.state === true, indicator_good: stat_rx.state === false, indicator: true }"></span></td></tr>
<tr><th>Захват символьной</th><td><span :class="{ indicator_bad: stat_rx.sym_sync_lock === true, indicator_good: stat_rx.sym_sync_lock === false, indicator: true }"></span></td></tr>
<tr><th>Захват ФАПЧ</th><td><span :class="{ indicator_bad: stat_rx.afc_lock === true, indicator_good: stat_rx.afc_lock === false, indicator: true }"></span></td></tr>
<tr><th>Захват поиска по частоте</th><td><span :class="{ indicator_bad: stat_rx.freq_search_lock === true, indicator_good: stat_rx.freq_search_lock === false, indicator: true }"></span></td></tr>
<tr><th>Захват пакетной синхр.</th><td><span :class="{ indicator_bad: stat_rx.pkt_sync === true, indicator_good: stat_rx.pkt_sync === false, indicator: true }"></span></td></tr>
<tr><th>ОСШ/RSSI</th><td>{{ stat_rx.snr }} / {{ stat_rx.rssi }}</td></tr>
<tr><th>Modcod/размер кадра</th><td>{{ stat_rx.modcod }} / {{ stat_rx.frameSize }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ stat_rx.pilots }}</td></tr>
<tr><th>Символьная ошибка</th><td>{{ stat_rx.symError }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ stat_rx.freqErr }} / {{ stat_rx.freqErrAcc }}</td></tr>
<tr><th>Ур. входного сигнала</th><td>{{ stat_rx.inputSignalLevel }}</td></tr>
<tr><th>Ошибка ФАПЧ</th><td>{{ stat_rx.pllError }}</td></tr>
<tr><th>Инф. скорость на приеме</th><td>{{ stat_rx.speedOnRxKbit }} kbit/s</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ stat_rx.speedOnIifKbit }} kbit/s</td></tr>
</tbody>
</table>
<p> Статистика пакетов </p>
<table>
<tbody>
<tr><th>Качественных пакетов</th><td>{{ stat_rx.packetsOk }}</td></tr>
<tr><th>Поврежденных пакетов</th><td>{{ stat_rx.packetsBad }}</td></tr>
<tr><th>DUMMY</th><td>{{ stat_rx.packetsDummy }}</td></tr>
</tbody>
</table>
<button> Сброс статистики </button>
</div>
<div>
<h2>Статистика передачи</h2>
<table>
<tbody>
<tr><th>Передача</th><td><span :class="{ indicator_bad: stat_tx.state === true, indicator_good: stat_tx.state === false, indicator: true }"></span></td></tr>
<tr><th>ОСШ дальнего приема</th><td>{{ stat_tx.snr }}</td></tr>
<tr><th>Modcod</th><td>{{ stat_tx.modcod }}</td></tr>
<tr><th>Размер кадра</th><td>{{ stat_tx.frameSize }}</td></tr>
<tr><th>Пилот-символы</th><td>{{ stat_tx.pilots }}</td></tr>
<tr><th>Инф. скорость на передаче</th><td>{{ stat_tx.speedOnTxKbit }} kbit/s</td></tr>
<tr><th>Инф. скорость на интерфейсе</th><td>{{ stat_tx.speedOnIifKbit }} kbit/s</td></tr>
</tbody>
</table>
</div>
<div v-if="isCinC === true">
<h2>Статистика режима CinC</h2>
<table>
<tbody>
<tr><th>ОСС</th><td>{{ stat_cinc.occ }}</td></tr>
<tr><th>Захват коррелятора</th><td><span :class="{ indicator_bad: stat_cinc.correlator === true, indicator_good: stat_cinc.correlator === false, indicator: true }"></span></td></tr>
<tr><th>Кол-во срывов коррелятора</th><td>{{ stat_cinc.correlatorFails }}</td></tr>
<tr><th>Грубая/точная част. ошибка, Гц</th><td>{{ stat_cinc.freqErr }} / {{ stat_cinc.freqErrAcc }}</td></tr>
<tr><th>Задержка в канале, мс</th><td>{{ stat_cinc.channelDelay }}</td></tr>
</tbody>
</table>
</div>
<div>
<h2>Состояние устройства</h2>
<table>
<tbody>
<tr><th>Температура ADRV</th><td>{{ stat_device.adrv }} °C</td></tr>
<tr><th>Температура ZYNC</th><td>{{ stat_device.zync }} °C</td></tr>
<tr><th>Температура FPGA</th><td>{{ stat_device.fpga }} °C</td></tr>
</tbody>
</table>
</div>
</div>
<div class="tabs-body-item tabs-item-flex-container" v-show="activeTab === 'setup'">
<div>
<h2>Настройка передатчика</h2>
<div>
<div>
...
</div>
<h3>Параметры передачи</h3>
<div>
...
</div>
<h3>Режим работы DVB-S2</h3>
<div>
...
</div>
</div>
</div>
<div>
<h2>Настройка приемника</h2>
</div>
<div>
<h2>Настройки режима CinC</h2>
<p>CinC пока нельзя настроить, но скоро разработчик это поправит)</p>
</div>
<div>
<h2>Настройки питания и опорного генератора</h2>
<p>Эти настройки пока недоступны, но скоро разработчик это поправит)</p>
</div>
</div>
<div class="tabs-body-item" v-show="activeTab === 'admin'">
<p>
Эти настройки пока недоступны, но скоро разработчик это поправит. А пока смотри на крокодила
</p>
<img src="/images/krokodil_vzryvaetsya_hd.gif" alt="krokodil">
</div>
</div>
<p>Последнее обновление статистики: {{ lastUpdateTime }}</p>
</div>
</div>
<!-- Версия для разработки включает в себя возможность вывода в консоль полезных уведомлений -->
<script src="/js/vue.js"></script>
<script>
// const router = useRouter();
const availableTabs = ['monitoring', 'setup', 'admin']
const defaultTab = availableTabs[0]
function getCurrentTab() {
const sl = window.location.hash.slice(1)
if (availableTabs.indexOf(sl) >= 0) {
return sl
}
return defaultTab
}
const app = new Vue({
el: '#app',
data: {
isCinC: null,
stat_rx: {
// индикаторы
state: '?', // общее состояние
sym_sync_lock: '?', // захват символьной
freq_search_lock: '?', // Захват поиска по частоте
afc_lock: '?', // захват ФАПЧ
pkt_sync: '?', // захват пакетной синхронизации
// куча других параметров, идет в том же порядке, что и в таблице
snr: '?', rssi: '?',
modcod: '?', frameSize: '?',
pilots: '?',
symError: '?',
freqErr: '?', freqErrAcc: '?',
inputSignalLevel: '?',
pllError: '?',
speedOnRxKbit: '?',
speedOnIifKbit: '?',
// статистика пакетов
packetsOk: '?', packetsBad: '?', packetsDummy: '?',
},
stat_tx: {
// состояние
state: '?',
// прочие поля
snr: '?', modcod: '?', frameSize: '?', pilots: '?', speedOnTxKbit: '?', speedOnIifKbit: '?',
},
stat_cinc: {
occ: '?',
correlator: null,
correlatorFails: '?',
freqErr: '?', freqErrAcc: '?',
channelDelay: '?'
},
stat_device: { // температурные датчики
adrv: 0, zync: 0, fpga: 0
},
testState: '?',
lastUpdateTime: new Date(),
activeTab: getCurrentTab()
},
methods: {
updateMainState(vals) {
this.lastUpdateTime = new Date();
this.isCinC = vals["mainState"]["isCinC"]
this.stat_rx.state = vals["mainState"]["rx.state"]
this.stat_rx.sym_sync_lock = vals["mainState"]["rx.sym_sync_lock"]
this.stat_rx.freq_search_lock = vals["mainState"]["rx.freq_search_lock"]
this.stat_rx.afc_lock = vals["mainState"]["rx.afc_lock"]
this.stat_rx.pkt_sync = vals["mainState"]["rx.pkt_sync"]
this.stat_rx.snr = vals["mainState"]["rx.snr"]
this.stat_rx.rssi = vals["mainState"]["rx.rssi"]
this.stat_rx.modcod = vals["mainState"]["rx.modcod"]
this.stat_rx.frameSize = vals["mainState"]["rx.frameSize"]
this.stat_rx.pilots = vals["mainState"]["rx.pilots"]
this.stat_rx.symError = vals["mainState"]["rx.symError"]
this.stat_rx.freqErr = vals["mainState"]["rx.freqErr"]
this.stat_rx.freqErrAcc = vals["mainState"]["rx.freqErrAcc"]
this.stat_rx.inputSignalLevel = vals["mainState"]["rx.inputSignalLevel"]
this.stat_rx.pllError = vals["mainState"]["rx.pllError"]
this.stat_rx.speedOnRxKbit = vals["mainState"]["rx.speedOnRxKbit"]
this.stat_rx.speedOnIifKbit = vals["mainState"]["rx.speedOnIifKbit"]
this.stat_rx.packetsOk = vals["mainState"]["rx.packetsOk"]
this.stat_rx.packetsBad = vals["mainState"]["rx.packetsBad"]
this.stat_rx.packetsDummy = vals["mainState"]["rx.packetsDummy"]
this.stat_tx.state = vals["mainState"]["tx.state"]
this.stat_tx.snr = vals["mainState"]["rx.snr"]
this.stat_tx.modcod = vals["mainState"]["rx.modcod"]
this.stat_tx.frameSize = vals["mainState"]["rx.frameSize"]
this.stat_tx.pilots = vals["mainState"]["rx.pilots"]
this.stat_tx.speedOnTxKbit = vals["mainState"]["tx.speedOnTxKbit"]
this.stat_tx.speedOnIifKbit = vals["mainState"]["rx.speedOnIifKbit"]
this.stat_cinc.occ = vals["mainState"]["cinc.occ"]
this.stat_cinc.correlator = vals["mainState"]["cinc.correlator"]
this.stat_cinc.correlatorFails = vals["mainState"]["cinc.correlatorFails"]
this.stat_cinc.freqErr = vals["mainState"]["cinc.freqErr"]
this.stat_cinc.freqErrAcc = vals["mainState"]["cinc.freqErrAcc"]
this.stat_cinc.channelDelay = vals["mainState"]["cinc.channelDelay"]
this.stat_device.adrv = vals["mainState"]["device.adrv"]
this.stat_device.zync = vals["mainState"]["device.zync"]
this.stat_device.fpga = vals["mainState"]["device.fpga"]
this.testState = vals["mainState"]["testState"]
}
},
mounted() {
const doFetch = async () => {
let d = await fetch("/api/mainStatistics")
this.updateMainState(await d.json())
setTimeout(() => {
doFetch()
}, 1000)
}
doFetch().then(() => {})
document.getElementById("app").removeAttribute("hidden")
}
})
// import MyComponent from './modules/header'
// const sh = new Vue(MyComponent)
</script>
</body>
</html>

View File

@@ -2,15 +2,17 @@
body {
--text-color: #262626;
--text-color2: #3d3d3d;
--text-good: green;
--text-bad: red;
--text-good: #0CF500;
--text-bad: #F5000C;
--brand-bg: #EDF3FE;
--brand-text: #5488F7;
--brand-bg: #B3C0D1;
--brand-text: #0146f4;
--bg-color: #FEFEFE;
--bg-selected: #F1F1F1;
--bg-action: #5181fe;
--bg-element: #a7a7a7;
--bg-action: #81a7ff;
--bg-danger: #ff6464;
}
@media (prefers-color-scheme: dark) {
@@ -18,15 +20,17 @@ body {
body {
--text-color: #eee;
--text-color2: #bbb;
--text-good: greenyellow;
--text-bad: orangered;
--text-good: #91FF00;
--text-bad: #FF1F2A;
--brand-bg: #393E50;
--brand-text: #5F93F3;
--bg-color: #2d2c33;
--bg-selected: #424248;
--bg-action: #4a70d5;
--bg-element: #626268;
--bg-action: #3a58af;
--bg-danger: #ac1e1e;
}
}
@@ -40,6 +44,7 @@ body {
}
body {
overflow-y: visible;
background: var(--bg-color);
margin: 0; /* браузеры зачем-то ставят свое значение */
}
@@ -48,31 +53,15 @@ body {
margin: 0.5em;
}
/* увеличение размера шрифтов */
* { font-size: large; }
h1 { font-size: xxx-large; }
h2 { font-size: xx-large; }
h3 { font-size: larger; }
/* ========== MAIN STYLES ========== */
header > h1 {
text-align: center;
background-color: var(--brand-bg);
padding: 0.5em;
margin: 0;
}
header * {
color: var(--brand-text);
}
header > nav {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
header > nav > a {
margin: 0.5em;
}
.value-good {
color: var(--text-good);
}