Compare commits

..

143 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
73 changed files with 51382 additions and 18664 deletions

6
.gitignore vendored
View File

@@ -5,3 +5,9 @@ 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,14 +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()
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)
add_subdirectory(dependencies/control_system)
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
@@ -45,8 +88,12 @@ add_executable(terminal-web-server
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)
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")
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();
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,121 +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_get_demodulator_state(demodulator_state_com &demodulator_state);
response_type send_get_modulator_state(modulator_state_com &modulator_state);
response_type send_get_device_state(device_state_com &device_state);
response_type send_get_cinc_state(CinC_state_com &cinc_state);
response_type send_set_modulator_settings(modulator_settings_com &settings);
response_type send_set_demodulator_settings(demodulator_settings_com &settings);
response_type send_get_modulator_settings(modulator_settings_com &settings);
response_type send_get_demodulator_settings(demodulator_settings_com &settings);
response_type send_set_buc_lnb_settings(buc_lnb_settings_com &settings);
response_type send_get_buc_lnb_settings(buc_lnb_settings_com &settings);
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);
response_type send_set_qos_settings_json(const std::string &json_string, bool is_enable);
response_type send_get_qos_settings_json(std::string &json_string, bool &is_enable);
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,865 +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
};
struct modulator_settings_com{
uint32_t baudrate;
double central_freq_in_kGz;
double rollof;
double attenuation;
bool is_test_data;
bool is_save_current_state;
bool is_carrier;
bool tx_is_on;
bool is_cinc;
uint32_t modcod_tx;
};
struct modulator_state_com{
bool is_tx_on;
float snr_remote;
uint16_t modcod;
bool is_short;
bool is_pilots;
uint32_t speed_in_bytes_tx;
uint32_t speed_in_bytes_tx_iface;
};
struct demodulator_locks_com{
bool pkt_sync;
bool afc_lock;
bool freq_lock;
bool sym_sync_lock;
};
struct demodulator_settings_com
{
uint32_t baudrate;
double central_freq_in_kGz;
double rollof;
bool is_aru_on;
bool is_rvt_iq;
double gain;
};
struct demodulator_state_com{
float snr;
uint16_t modcod;
bool is_short;
bool is_pilots;
float rssi;
double afc_err;
double crs_freq_err;
double sym_err;
double fine_freq_err;
double if_overload;
uint32_t packet_ok_cnt;
uint32_t packet_bad_cnt;;
uint32_t dummy_cnt;
uint32_t speed_in_bytes_rx;
uint32_t speed_in_bytes_rx_iface;
demodulator_locks_com locks;
};
struct CinC_state_com{
float ratio_signal_signal;
bool carrier_lock;
int32_t freq_error_offset;
uint32_t delay_dpdi;
int32_t freq_fine_estimate;
uint32_t cnt_bad_lock;
};
struct device_state_com{
double adrv_temp;
double zynq_temp;
double pl_temp;
};
enum class voltage_lnb_com{
DISABLE = 0, _13V, _18V, _24V
};
enum class voltage_buc_com{
DISABLE = 0, _24V, _48V
};
struct buc_lnb_settings_com
{
voltage_lnb_com lnb;
bool is_ref_10MHz_lnb = false;
voltage_buc_com buc;
bool is_ref_10MHz_buc = false;
bool is_ref_10MHz_output = false;
bool is_save_current_state = false;
};
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,
get_demodulator_state = 31,
get_modulator_state = 32,
get_cinc_state = 33,
get_device_state = 34,
set_modulator_settings = 35,
set_demodulator_settings = 36,
get_modulator_settings = 37,
get_demodulator_settings = 38,
set_lnb_buc_settings = 39,
get_lnb_buc_settings = 40,
set_qos_settings_json = 41,
get_qos_settings_json = 42
};
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_lnb_buc_settings{
buc_lnb_settings_com buc_lnb;
template<class Archive>
void serialize(Archive & archive)
{
archive(buc_lnb.buc, buc_lnb.is_ref_10MHz_buc, buc_lnb.lnb,buc_lnb.is_ref_10MHz_lnb, buc_lnb.is_ref_10MHz_output, buc_lnb.is_save_current_state);
}
};
struct cmd_get_cinc_state
{
CinC_state_com cinc_state;
template<class Archive>
void serialize(Archive & archive)
{
archive(cinc_state.carrier_lock, cinc_state.cnt_bad_lock,
cinc_state.delay_dpdi, cinc_state.freq_error_offset,
cinc_state.freq_fine_estimate,
cinc_state.ratio_signal_signal);
}
};
struct cmd_get_device_state
{
device_state_com device_state;
template<class Archive>
void serialize(Archive & archive)
{
archive(device_state.adrv_temp, device_state.pl_temp, device_state.zynq_temp);
}
};
struct cmd_demodulator_settings
{
demodulator_settings_com demodulator_settings;
template<class Archive>
void serialize(Archive & archive)
{
archive(demodulator_settings.baudrate,demodulator_settings.central_freq_in_kGz,
demodulator_settings.gain, demodulator_settings.is_aru_on,
demodulator_settings.is_rvt_iq, demodulator_settings.rollof);
}
};
struct cmd_get_demodulator_state
{
demodulator_state_com demodulator_state;
template<class Archive>
void serialize(Archive & archive)
{
archive(demodulator_state.snr, demodulator_state.modcod, demodulator_state.is_short, demodulator_state.is_pilots,
demodulator_state.rssi, demodulator_state.afc_err, demodulator_state.crs_freq_err,
demodulator_state.sym_err, demodulator_state.fine_freq_err, demodulator_state.if_overload,
demodulator_state.packet_ok_cnt, demodulator_state.packet_bad_cnt, demodulator_state.dummy_cnt,
demodulator_state.speed_in_bytes_rx, demodulator_state.speed_in_bytes_rx_iface,
demodulator_state.locks.afc_lock, demodulator_state.locks.freq_lock, demodulator_state.locks.pkt_sync, demodulator_state.locks.sym_sync_lock );
}
};
struct cmd_modulator_settings
{
modulator_settings_com modulator_settings;
template<class Archive>
void serialize(Archive & archive)
{
archive(modulator_settings.attenuation,modulator_settings.baudrate, modulator_settings.central_freq_in_kGz,
modulator_settings.is_carrier, modulator_settings.is_cinc,
modulator_settings.is_save_current_state, modulator_settings.is_test_data,
modulator_settings.rollof, modulator_settings.tx_is_on, modulator_settings.modcod_tx);
}
};
struct cmd_get_modulator_state{
modulator_state_com modulator_state;
template<class Archive>
void serialize(Archive & archive)
{
archive(modulator_state.snr_remote, modulator_state.modcod, modulator_state.is_short, modulator_state.is_pilots,
modulator_state.is_tx_on, modulator_state.speed_in_bytes_tx, modulator_state.speed_in_bytes_tx_iface);
}
};
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_qos_settings{
std::string json_string;
bool is_enable;
template<class Archive>
void serialize(Archive & archive)
{
archive(json_string, is_enable);
}
};
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,76 +0,0 @@
#include "include/terminal_api/ControlProtoCInterface.h"
#include <iostream>
int main()
{
TSID sid{0};
unsigned int access{0};
if(CP_Login("admin","pass",&sid,&access) == OK)
{
std::cout << "Succsec" << std::endl;
}
CinC_state state_cinc;
state_cinc.carrier_lock = false;
CP_GetCinCState(sid,state_cinc);
std::cout << state_cinc.carrier_lock << std::endl;
std::cout << state_cinc.freq_error_offset << std::endl;
std::cout << state_cinc.delay_dpdi << std::endl;
std::cout << state_cinc.cnt_bad_lock << std::endl;
modulator_state modulator;
CP_GetModulatorState(sid, modulator);
std::cout << modulator.is_pilots << "\n"
<< modulator.modcod << "\n"
<< modulator.snr_remote << "\n";
demodulator_state demodulator;
demodulator.dummy_cnt = 11111;
CP_GetDemodulatorState(sid, demodulator);
std::cout << "end CP_GetDemodulatorState" << std::endl;
std::cout << demodulator.is_short << std::endl;
std::cout << demodulator.modcod << std::endl;
std::cout << "afc_lock: " <<demodulator.locks.afc_lock << std::endl;
std::cout << "sym_sync_lock: " <<demodulator.locks.sym_sync_lock << std::endl;
std::cout << "freq_lock: " <<demodulator.locks.freq_lock << std::endl;
std::cout << "pkt_sync: " << demodulator.locks.pkt_sync << std::endl;
modulator_settings setting_modulator;
setting_modulator.baudrate = 2000000;
setting_modulator.central_freq_in_kGz = 1340000.24;
setting_modulator.rollof = 0.25;
setting_modulator.tx_is_on = true;
setting_modulator.is_test_data = false;
setting_modulator.is_save_current_state = true;
setting_modulator.is_cinc = false;
setting_modulator.is_carrier = true;
CP_SetModulatorSettings(sid, setting_modulator);
demodulator_settings demodulator_sett;
demodulator_sett.baudrate = 1340000;
demodulator_sett.central_freq_in_kGz = 2400000.34;
demodulator_sett.is_rvt_iq = true;
demodulator_sett.is_aru_on = true;
demodulator_sett.gain = 13;
demodulator_sett.rollof = 0.15;
if(CP_SetDemodulatorSettings(sid, demodulator_sett)== OK)
{
std::cout << "OK SET DEMODULATOR SETTINGS" << std::endl;
}
demodulator_settings demodulator_settings_;
if(CP_GetDemodulatorSettings(sid,demodulator_settings_) == OK)
{
std::cout << "OK GET DEMODULATOR SETTINGS" << std::endl;
std::cout << demodulator_settings_.baudrate << std::endl;
std::cout << demodulator_settings_.gain<< std::endl;
std::cout << demodulator_settings_.rollof << std::endl;
std::cout << demodulator_settings_.is_aru_on << std::endl;
std::cout << demodulator_settings_.is_rvt_iq << std::endl;
std::cout << demodulator_settings_.central_freq_in_kGz << std::endl;
}
modulator_settings modulator_settings_;
if(CP_GetModulatorSettings(sid,modulator_settings_)== OK)
{
std::cout << "OK GET MODULATOR SETTINGS" << std::endl;
std::cout << modulator_settings_.baudrate << std::endl;
std::cout << modulator_settings_.attenuation << std::endl;
std::cout << modulator_settings_.central_freq_in_kGz << std::endl;
std::cout << modulator_settings_.is_carrier << std::endl;
std::cout << modulator_settings_.is_cinc << std::endl;
std::cout << modulator_settings_.rollof << std::endl;
}
}

View File

@@ -1,329 +0,0 @@
#ifndef __CONTROL_PROTO_COMMANDS__
#define __CONTROL_PROTO_COMMANDS__
#include <stdint.h>
#include <iostream>
#include <string>
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#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);
struct modulator_state{
bool is_tx_on;
float snr_remote;
uint16_t modcod;
bool is_short;
bool is_pilots;
uint32_t speed_in_bytes_tx;
uint32_t speed_in_bytes_tx_iface;
};
EXTERNC CP_Result CP_GetModulatorState(TSID sid, modulator_state &state);
struct demodulator_locks{
bool pkt_sync;
bool afc_lock;
bool freq_lock;
bool sym_sync_lock;
};
struct demodulator_state{
float snr;
uint16_t modcod;
bool is_short;
bool is_pilots;
float rssi;
double afc_err;
double crs_freq_err;
double sym_err;
double fine_freq_err;
double if_overload;
uint32_t packet_ok_cnt;
uint32_t packet_bad_cnt;
uint32_t dummy_cnt;
uint32_t speed_in_bytes_rx;
uint32_t speed_in_bytes_rx_iface;
demodulator_locks locks;
};
EXTERNC CP_Result CP_GetDemodulatorState(TSID sid, demodulator_state &state);
struct CinC_state{
float ratio_signal_signal;
bool carrier_lock;
int32_t freq_error_offset;
uint32_t delay_dpdi;
int32_t freq_fine_estimate;
uint32_t cnt_bad_lock;
};
EXTERNC CP_Result CP_GetCinCState(TSID sid, CinC_state &state);
struct device_state{
double adrv_temp;
double zynq_temp;
double pl_temp;
};
EXTERNC CP_Result CP_GetDeviceState(TSID sid, device_state &state);
struct modulator_settings{
uint32_t baudrate;
double central_freq_in_kGz;
double rollof;
double attenuation;
bool is_test_data;
bool is_save_current_state;
bool is_carrier;
bool tx_is_on;
bool is_cinc;
uint32_t modcod_tx;
};
EXTERNC CP_Result CP_SetModulatorSettings(TSID sid, modulator_settings& settings);
EXTERNC CP_Result CP_GetModulatorSettings(TSID sid, modulator_settings& settings);
struct demodulator_settings
{
uint32_t baudrate;
double central_freq_in_kGz;
double rollof;
bool is_aru_on;
bool is_rvt_iq;
double gain;
};
EXTERNC CP_Result CP_SetDemodulatorSettings(TSID sid, demodulator_settings& settings);
EXTERNC CP_Result CP_GetDemodulatorSettings(TSID sid, demodulator_settings& settings);
enum class voltage_lnb{
DISABLE = 0, _13V, _18V, _24V
};
enum class voltage_buc{
DISABLE = 0, _24V, _48V
};
struct buc_lnb_settings
{
voltage_lnb lnb;
bool is_ref_10MHz_lnb = false;
voltage_buc buc;
bool is_ref_10MHz_buc = false;
bool is_ref_10MHz_output = false;
bool is_save_current_state = false;
};
EXTERNC CP_Result CP_SetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings);
EXTERNC CP_Result CP_GetBUC_LNB_settings(TSID sid, buc_lnb_settings &settings);
EXTERNC CP_Result CP_SetQoSSettings(TSID sid, const std::string &qos_settings_json, bool is_enable);
EXTERNC CP_Result CP_GetQoSSettings(TSID sid, std::string &qos_settings_json, bool &is_enable);
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]}")

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

View File

@@ -1,8 +1,15 @@
#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() {
@@ -16,37 +23,45 @@ void http::auth::jwt::generateSecretKey() {
}
}
// payload, signature
static std::pair<std::string, std::string> parseJwtFromCookie(const std::string& input) {
std::string val1, val2;
size_t dotPos = input.find('.');
// Если точка найдена
if (dotPos != std::string::npos) {
val1 = input.substr(0, dotPos);
// Если в val1 есть еще точки, нужно найти последнюю точку
size_t lastDotPos = val1.find_last_of('.');
if (lastDotPos != std::string::npos) {
val1 = val1.substr(0, lastDotPos + 1);
}
val2 = input.substr(dotPos + 1);
} else {
// Точка не найдена, val1 - вся строка
val1 = input;
}
return std::make_pair(http::utils::b64Decode(val1), val2);
}
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()) {
auto tmp = parseJwtFromCookie(pc.at("auth"));
t.payload = tmp.first;
t.signature = tmp.second;
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;
@@ -55,6 +70,7 @@ http::auth::jwt::Jwt http::auth::jwt::Jwt::fromCookies(const std::string &cookie
http::auth::jwt::Jwt http::auth::jwt::Jwt::fromUser(const std::string &user) {
Jwt t;
t.payload = user;
t.lastUpdate = milliseconds();
return t;
}
@@ -63,18 +79,40 @@ bool http::auth::jwt::Jwt::isValid() {
return false;
}
auto realSignature = utils::sha256(this->payload + secretKey);
return signature == realSignature;
// проверка сигнатуры не нужна, она была на стадии парсинга куки
// 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() {
signature = utils::sha256(this->payload + secretKey);
auto val = utils::b64Encode(payload) + "." + signature;
return "auth=" + val + ";Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";
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;

View File

@@ -6,7 +6,10 @@
namespace http::auth::jwt {
extern std::string secretKey;
constexpr const char* EMPTY_AUTH_COOKIE = "auth=;Path=/; Max-Age=86400; HttpOnly; SameSite=Lax";;
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();
@@ -17,6 +20,7 @@ namespace http::auth::jwt {
*/
class Jwt {
std::string payload;
int64_t lastUpdate = 0;
std::string signature;
public:
static Jwt fromCookies(const std::string& cookie);
@@ -26,7 +30,9 @@ namespace http::auth::jwt {
std::string getUsername();
std::string asCookie();
std::string asCookie(bool isSecure = false);
bool needUpdate() const;
~Jwt();
};

View File

@@ -44,12 +44,12 @@ 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, server::Reply &rep) {
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()});
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)";
@@ -60,13 +60,17 @@ std::shared_ptr<http::auth::User> http::auth::AuthProvider::doAuth(const std::st
return nullptr;
}
std::shared_ptr<http::auth::User> http::auth::AuthProvider::getSession(const server::Request &req) {
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;
}
}
@@ -84,7 +88,7 @@ http::auth::AuthRequiredResource::AuthRequiredResource(const std::string &path,
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)) {
if (auto user = this->provider_.getSession(req, rep)) {
if (user->checkPremisions(this->perms)) {
this->generator_(req, rep);
return;

View File

@@ -51,6 +51,7 @@ namespace http::auth {
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; // использование функций для разработчиков
/**
* Проверить, что у пользователя есть нужное право. Если это суперпользователь, то у него по умолчанию все права есть.
@@ -70,20 +71,23 @@ namespace http::auth {
* @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, server::Reply &rep);
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);
std::shared_ptr<User> getSession(const server::Request &req, server::Reply &rep);
~AuthProvider();
};

View File

@@ -113,7 +113,20 @@ std::map<std::string, std::string> http::utils::parseCookies(const std::string&
if (equalPos == std::string::npos) {
continue; // Неверный формат Cookie
}
std::string name = cookie.substr(0, equalPos);
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

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

File diff suppressed because it is too large Load Diff

View File

@@ -11,40 +11,23 @@
#include <boost/log/utility/setup/formatter_parser.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <cstddef>
#include <memory>
#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>
constexpr const char* REBOOT_COMMAND = "web-action reboot";
constexpr const char* UPGRADE_COMMAND = "web-action upgrade";
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* FIRMWARE_LOCATION = "/tmp/firmware.zip";
namespace mime_types = http::server::mime_types;
@@ -57,23 +40,18 @@ 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();
@@ -84,8 +62,10 @@ class ServerResources {
std::unique_ptr<api_driver::ApiDriver> api;
http::auth::AuthProvider auth{};
bool upgradeOrRebootRunning = false;
static void onUploadFirmware(const http::server::Request& req) {
std::ofstream f("/tmp/firmware.zip", std::ios::binary);
std::ofstream f(FIRMWARE_LOCATION, std::ios::binary);
if (f.is_open()) {
f.write(req.payload.data(), static_cast<long>(req.payload.size()));
@@ -96,44 +76,89 @@ class ServerResources {
}
void doTerminalUpgrade() const {
api->executeInApi([](TSID sid) {
CP_SetDmaDebug(sid, "begin_save_config", "");
system(UPGRADE_COMMAND);
CP_SetDmaDebug(sid, "save_config", "");
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", "");
});
}
#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:
static constexpr const char* INDEX_HTML = "static/main.html";
static constexpr const char* LOGIN_HTML = "static/login.html";
#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 = "static/favicon.png";
static constexpr const char* VUE_JS = "static/js/vue.js"; // это тоже можно кешировать
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 = "static/style.css";
static constexpr const char* FIELDS_CSS = "static/fields.css";
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;
ServerResources(): sf(std::make_unique<http::resource::StaticFileFactory>()), api(std::make_unique<api_driver::ApiDriver>()) {
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::SUPERUSER));
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));
sf->registerFile(FAVICON_ICO, mime_types::image_png, true);
sf->registerFile(VUE_JS, mime_types::javascript, true);
// пароль fuckyou123
auth.users.emplace_back(std::make_shared<http::auth::User>("developer", "10628cfc434fb87f31d675d37e0402c2d824cfe8393aff7a61ee57aaa7d909c3", http::auth::User::SUPERUSER));
sf->registerFile(STYLE_CSS, mime_types::text_css, true);
sf->registerFile(FIELDS_CSS, mime_types::text_css, true);
sf->registerFile(INDEX_HTML, mime_types::text_html, false);
sf->registerFile(LOGIN_HTML, mime_types::text_html, true);
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);
auto user = auth.getSession(req, rep);
if (user == nullptr) {
http::server::httpRedirect(rep, "/login");
} else {
@@ -143,7 +168,7 @@ public:
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);
auto user = auth.getSession(req, rep);
if (user == nullptr) {
sf->serve(LOGIN_HTML, rep);
} else {
@@ -154,11 +179,9 @@ public:
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
try {
std::istringstream is(std::string(req.payload.data(), req.payload.size()));
boost::property_tree::ptree pt;
read_json(is, pt);
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
auto u = auth.doAuth(pt.get<std::string>("username"), pt.get<std::string>("password"), rep);
auto u = auth.doAuth(reqJson["username"], reqJson["password"], req, rep);
if (u == nullptr) {
throw std::runtime_error("invalid session");
}
@@ -182,249 +205,287 @@ public:
}
}));
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>("/js/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>(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();
}
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 += "}";
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();
}
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"settings":)";
result += api->loadSettings();
result += "}";
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const auto result = api->loadFirmwareVersion();
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.clear();
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setQosSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/bucLnb", this->auth, http::auth::User::SUPERUSER, [this](const auto& req, auto& rep) {
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setBucLnbSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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 settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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();
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/cinc", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setCincSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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/cinc): Can't set CinC settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setRxTxSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
api->setNetworkSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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/rxtx): Can't set RX/TX settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/network): Can't set network settings: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/set/debugSend", this->auth, http::auth::User::EDIT_SETTINGS, [this](const auto& req, auto& rep) {
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.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
nlohmann::json resultJson;
try {
std::stringstream ss;
ss.str(std::string(req.payload.begin(), req.payload.end()));
boost::property_tree::ptree pt;
read_json(ss, pt);
auto reqJson = nlohmann::json::parse(std::string(req.payload.begin(), req.payload.end()));
auto password = reqJson["password"].get<std::string>();
api->setDebugSendSettings(pt);
std::string result = R"({"status":"ok","settings":)";
result += api->loadSettings();
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
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/rxtx): Can't set RX/TX settings: " << e.what();
const std::string result = R"({"status":"error"})";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/cesPassword): Can't set CES password: " << e.what();
resultJson.clear();
resultJson["status"] = "error";
resultJson["error"] = e.what();
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/reboot", this->auth, 0, [](const auto& req, auto& rep) {
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.clear();
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::SUPERUSER, [this](const auto& req, auto& rep) {
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.clear();
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());
@@ -432,14 +493,15 @@ public:
system(REBOOT_COMMAND);
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/firmwareUpdate", this->auth, http::auth::User::UPDATE_FIRMWARE, [](const auto& req, auto& rep) {
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.clear();
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());
@@ -447,19 +509,166 @@ public:
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;
}
doTerminalUpgrade();
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
const auto result = api->loadFirmwareVersion();
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;
@@ -473,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;
}
@@ -485,9 +714,12 @@ 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
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;
@@ -496,20 +728,22 @@ int main(int argc, char *argv[]) {
BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey;
#endif
ServerResources resources;
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]);
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);
@@ -523,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);
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,13 +3,13 @@
#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() {
@@ -51,11 +51,12 @@ namespace http::server {
void Connection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.http_version_major == 1) {
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) {
@@ -71,7 +72,7 @@ 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() {
@@ -88,6 +89,7 @@ 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();
@@ -125,11 +127,12 @@ namespace http::server {
void SslConnection::doWrite() {
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
if (request_.http_version_major == 1) {
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) {

View File

@@ -21,7 +21,7 @@ namespace http::server {
/// A request received from a client.
class Request {
public:
Request();
Request(bool secure);
void reset();
std::string getHeaderValue(const std::string& headerName) const;
@@ -29,9 +29,10 @@ namespace http::server {
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;

View File

@@ -51,7 +51,7 @@ namespace http::server {
Url::~Url() = default;
Request::Request() = default;
Request::Request(bool secure): isSecure(secure) {}
void Request::reset() {
method = "";
@@ -59,9 +59,9 @@ namespace http::server {
if (url != nullptr) {
url.reset(nullptr);
}
is_keep_alive = false;
http_version_major = 0;
http_version_minor = 0;
isKeepAlive = false;
httpVersionMajor = 0;
httpVersionMinor = 0;
headers.clear();
payload.clear();
}
@@ -151,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 {
@@ -160,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 {
@@ -171,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 {
@@ -189,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;

View File

@@ -3,10 +3,10 @@
#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();
@@ -22,37 +22,42 @@ static void loadFile(const std::string& path, std::vector<char>& content) {
http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {}
http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache): path(std::move(path)), type(type), allowCache(allowCache) {
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->path;
loadFile(this->path, this->content);
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->webPath;
loadFile(path, this->content);
} else {
BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->path;
BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->webPath;
}
#else
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path;
loadFile(this->path, this->content);
BOOST_LOG_TRIVIAL(info) << "Load static file " << path;
loadFile(path, this->content);
#endif
}
http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default;
http::resource::StaticFileFactory::StaticFileFactory() = default;
void http::resource::StaticFileFactory::registerFile(const std::string &path, server::mime_types::Mime type, bool allowCache) {
this->files.emplace_back(path, type, allowCache);
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);
}
void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) {
for (auto& f: this->files) {
if (f.path == path) {
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.path, rep.content);
loadFile(f.fsPath, rep.content);
}
#else
rep.content.clear();

View File

@@ -23,9 +23,12 @@ namespace http::resource {
class StaticFileFactory {
class StaticFileDef {
public:
StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache = true);
StaticFileDef(const std::string& path, std::string webPath, server::mime_types::Mime type, bool allowCache = true);
std::string path;
std::string webPath;
#ifdef USE_DEBUG
std::string fsPath;
#endif
server::mime_types::Mime type;
bool allowCache;
std::vector<char> content;
@@ -36,7 +39,7 @@ namespace http::resource {
public:
StaticFileFactory();
void registerFile(const std::string& path, server::mime_types::Mime type, bool allowCache = true);
void registerFile(const std::string& path, const std::string &webPath, server::mime_types::Mime type, bool allowCache = true);
void serve(const std::string& path, server::Reply& rep);
@@ -59,6 +62,8 @@ namespace http::resource {
~GenericResource() override;
};
void loadFile(const std::string& path, std::vector<char>& content);
}
#endif //RESOURCE_H

View File

@@ -23,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,
@@ -39,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);
@@ -48,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,
@@ -63,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);

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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,16 @@
#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 <boost/property_tree/ptree.hpp>
#include <terminal_api/ControlProtoCInterface.h>
#include "common/nlohmann/json.hpp"
namespace api_driver {
constexpr int CACHE_STATISTICS_UPDATE_MS = 500;
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;
@@ -28,53 +30,84 @@ namespace api_driver {
/**
* Запросить общее состояние терминала
* @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() const;
nlohmann::json loadTerminalState() const;
/**
* Сбросить статистику пакетов
*/
void resetPacketStatistics() const;
std::string loadSettings() const;
nlohmann::json loadSettings() const;
std::string loadFirmwareVersion() const;
nlohmann::json loadFirmwareVersion() const;
/**
* Установить настройки RX/TX, readback можно получить используя loadTerminalState
*/
void setRxTxSettings(boost::property_tree::ptree &pt);
* Установить настройки RX/TX, readback можно получить используя loadTerminalState
*/
void setRxTxSettings(const nlohmann::json& data);
#ifdef API_OBJECT_DPDI_SETTINGS_ENABLE
/**
* Установить настройки CinC, readback можно получить используя loadTerminalState.
*/
void setCincSettings(boost::property_tree::ptree &pt);
* Установить настройки 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(boost::property_tree::ptree &pt);
* Установить настройки BUC и LNB, readback можно получить используя loadTerminalState.
*/
void setBucLnbSettings(const nlohmann::json& data);
#endif
#ifdef API_OBJECT_QOS_SETTINGS_ENABLE
/**
* Установить настройки QoS, readback можно получить используя loadTerminalState.
*/
void setQosSettings(boost::property_tree::ptree &pt);
* Установить настройки QoS, readback можно получить используя loadTerminalState.
*/
void setQosSettings(const nlohmann::json& data);
#endif
void setNetworkSettings(boost::property_tree::ptree & pt);
void setDebugSendSettings(boost::property_tree::ptree & pt);
#ifdef API_OBJECT_NETWORK_SETTINGS_ENABLE
void setNetworkSettings(const nlohmann::json& data);
#endif
void resetDefaultSettings();
void executeInApi(const std::function<void(TSID sid)>& callback);
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
*/
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:
std::string deviceInitState;
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>

View File

@@ -7,7 +7,6 @@
}
.tabs-btn {
text-decoration: none;
font-size: 18px;
border: none;
padding: 10px 25px;
text-align: center;
@@ -85,13 +84,14 @@
padding: 1em;
}
.settings-set-container th {
.statistics-container th {
text-align: left;
padding-right: 1em;
}
.settings-set-container td {
min-width: 10em;
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;
}
@@ -134,9 +134,10 @@ select * {
color: var(--text-color);
}
.settings-set-container tr > * {
border-bottom: solid 1px var(--text-color2);
.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);

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

View File

@@ -111,7 +111,8 @@
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
body: JSON.stringify(requestData),
credentials: 'same-origin'
}).then(response => {
// Обработка ответа сервера
response.json().then((value) => {

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>

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ body {
--bg-color: #FEFEFE;
--bg-selected: #F1F1F1;
--bg-element: #a7a7a7;
--bg-action: #5181fe;
--bg-danger: #db2828;
--bg-action: #81a7ff;
--bg-danger: #ff6464;
}
@media (prefers-color-scheme: dark) {
@@ -29,7 +29,8 @@ body {
--bg-color: #2d2c33;
--bg-selected: #424248;
--bg-element: #626268;
--bg-action: #4a70d5;
--bg-action: #3a58af;
--bg-danger: #ac1e1e;
}
}
@@ -52,6 +53,13 @@ body {
margin: 0.5em;
}
/* увеличение размера шрифтов */
* { font-size: large; }
h1 { font-size: xxx-large; }
h2 { font-size: xx-large; }
h3 { font-size: larger; }
/* ========== MAIN STYLES ========== */
.value-good {