какая-то куча изменений

This commit is contained in:
Vladislav Ostapov 2025-05-27 10:55:50 +03:00
parent 17cdd69207
commit 3745f98a82
10 changed files with 1444 additions and 6 deletions

3
.gitignore vendored
View File

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

View File

@ -1,8 +1,8 @@
stages: stages:
- deploy - build
test for build: test for build:
stage: deploy stage: build
image: localhost:5000/cpp-test-universal:latest image: localhost:5000/cpp-test-universal:latest
tags: tags:
- cpp-test-universal - cpp-test-universal
@ -13,10 +13,14 @@ test for build:
- git submodule update - git submodule update
- cmake -DCMAKE_BUILD_TYPE=Debug -DMODEM_TYPE=TDMA -B cmake-build-debug-tdma - 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=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=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=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-tdma -j 4
- cmake --build cmake-build-debug-scpc -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-tdma -j 4
- cmake --build cmake-build-release-scpc -j 4 - cmake --build cmake-build-release-scpc -j 4
- cmake --build cmake-build-release-shps -j 4

View File

@ -21,8 +21,11 @@ if("${MODEM_TYPE}" STREQUAL "SCPC")
elseif ("${MODEM_TYPE}" STREQUAL "TDMA") elseif ("${MODEM_TYPE}" STREQUAL "TDMA")
add_definitions(-DMODEM_IS_TDMA) add_definitions(-DMODEM_IS_TDMA)
message(STATUS "Selected TDMA modem") message(STATUS "Selected TDMA modem")
elseif ("${MODEM_TYPE}" STREQUAL "SHPS")
add_definitions(-DMODEM_IS_SHPS)
message(STATUS "Selected TDMA modem")
else() else()
message(FATAL_ERROR "You must set `MODEM_TYPE` \"SCPC\" or \"TDMA\"!") message(FATAL_ERROR "You must set `MODEM_TYPE` \"SCPC\" or \"TDMA\" or \"SHPS\"!")
endif() endif()
SET(PROJECT_GIT_REVISION "0") SET(PROJECT_GIT_REVISION "0")
@ -54,6 +57,10 @@ add_subdirectory(dependencies/control_system_client)
include_directories(src/) include_directories(src/)
add_executable(terminal-web-server add_executable(terminal-web-server
src/api-driver/proxy.h
src/api-driver/proxy.cpp
src/api-driver/structs.h
src/api-driver/structs.cpp
src/server/mime_types.hpp src/server/mime_types.hpp
src/server/mime_types.cpp src/server/mime_types.cpp
src/server/request_parser.hpp src/server/request_parser.hpp

View File

@ -350,6 +350,128 @@
{"name": "qos", "desc": "QoS"}, {"name": "qos", "desc": "QoS"},
{"name": "admin", "desc": "Администрирование"} {"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": "select", "label": "Входные данные", "name": "txIsTestInput",
"values": [{"label": "Ethernet", "value": "false"}, {"label": "Тест", "value": "true"}]
},
{"widget": "h3", "label": "Параметры передачи"},
{"widget": "number", "label": "Центральная частота, КГц", "name": "txCentralFreq", "min": 950000, "max": 6000000, "step": 0.01},
{"widget": "number", "label": "Символьная скорость, Бод", "name": "txBaudrate", "min": 200000, "max": 54000000},
{
"widget": "select", "label": "Roll-off", "name": "txRolloff",
"values": [{"label": "0.02", "value": "2"}, {"label": "0.05", "value": "5"}, {"label": "0.10", "value": "10"}, {"label": "0.15", "value": "15"}, {"label": "0.20", "value": "20"}, {"label": "0.25", "value": "25"}]
},
{"widget": "number", "label": "Коэф. расширения", "name": "txSpreadCoef", "max": 1000, "min": -1000, "step": 0.01},
{
"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": "Настройки приемника"},
{
"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", "label": "Центральная частота, КГц", "name": "rxCentralFreq", "min": 950000, "max": 6000000, "step": 0.01},
{"widget": "number", "label": "Символьная скорость, Бод", "name": "rxBaudrate", "min": 200000, "max": 54000000},
{
"widget": "select", "label": "Roll-off", "name": "rxRolloff",
"values": [{"label": "0.02", "value": "2"}, {"label": "0.05", "value": "5"}, {"label": "0.10", "value": "10"}, {"label": "0.15", "value": "15"}, {"label": "0.20", "value": "20"}, {"label": "0.25", "value": "25"}]
},
{"widget": "number", "label": "Коэф. расширения", "name": "rxSpreadCoef", "max": 1000, "min": -1000, "step": 0.01},
{
"widget": "select", "label": "Номер последовательности Голда", "name": "rxGoldan",
"values": [{"label": "0", "value": "0"}, {"label": "1", "value": "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"}
]
}
]
}
]
},
"tabs": [
{"name": "monitoring", "desc": "Мониторинг"},
{"name": "setup", "desc": "Настройки"},
{"name": "admin", "desc": "Администрирование"}
]
} }
} }
} }

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

@ -0,0 +1 @@
#include "proxy.h"

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

@ -0,0 +1,57 @@
#ifndef PROXY_H
#define PROXY_H
#include <boost/log/trivial.hpp>
#include <terminal_api/ControlProtoCInterface.h>
std::ostream& operator<<(std::ostream& out, CP_Result result);
namespace api_driver {
/**
* Функция-хелпер для логгирования ошибок всех вызовов API.
* @return то же значение ошибки, что и было передано в качестве параметра
*/
CP_Result logCpApiError(const char* desc, CP_Result err) {
if (err != OK) {
BOOST_LOG_TRIVIAL(error) << "CP API error in " << desc << ": " << err;
}
return err;
}
}
#define CPAPI_PROXY_CALL(proxy, func, ...) do { auto _res = func(proxy.sid, __VA_ARGS__); if (_res != OK) { BOOST_LOG_TRIVIAL(error) << "CP API error in " #func "(" #__VA_ARGS__ "): " << _res; } } while (0)
// void foo() {
// api_driver::proxy::CpProxy proxy;
// std::string tmp;
// CPAPI_PROXY_CALL(proxy, CP_GetDmaDebug, "fuck", &tmp);
// }
namespace api_driver::proxy {
class CpProxy {
public:
TSID sid;
CpProxy();
CpProxy(TSID s);
std::string getDmaDebug(TSID sid, const std::string& arg, const char* calledFrom = "");
void getModState(TSID sid, modulator_state& dest, const char* calledFrom = "");
void getModSettings(TSID sid, modulator_settings& dest, const char* calledFrom = "");
void setModSettings(TSID sid, modulator_settings& dest, const char* calledFrom = "");
void getDemodState(TSID sid, demodulator_state& dest, const char* calledFrom = "");
void getDemodSettings(TSID sid, demodulator_settings& dest, const char* calledFrom = "");
void setDemodSettings(TSID sid, demodulator_settings& dest, const char* calledFrom = "");
#ifdef MODEM_IS_SCPC
void getCincState(TSID sid, demodulator_settings& dest, const char* calledFrom = "");
#endif
~CpProxy();
};
}
#endif //PROXY_H

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

@ -0,0 +1,206 @@
#include "api-driver/structs.h"
#include <iomanip>
#include "api-driver/proxy.h"
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();
}
bool api_driver::obj::CpUpdatebleObject::checkNeedUpdate(int64_t now) const {
if (updatePeriodMs < 0) return false;
// тут нет смысла спать меньше чем на 20мс, поэтому можно разрешить чтение на некоторое время раньше
return now - lastUpdate >= (updatePeriodMs - 20);
}
int64_t api_driver::obj::CpUpdatebleObject::getNextUpdate(int64_t now) const {
if (checkNeedUpdate(now)) {
return 0;
}
auto next = now - lastUpdate;
return next < 0 ? 0 : next;
}
api_driver::obj::CpUpdatebleObject::~CpUpdatebleObject() = default;
void api_driver::obj::TerminalState::updateCallback(TSID sid, CP_Result &lastCpError) {
constexpr const char* thisFunc = "api_driver::obj::TerminalState::load()";
modulator_state modulator{};
demodulator_state demodulator{};
#ifdef MODEM_IS_SCPC
CinC_state cinc{};
#endif
proxy::getModState(sid, modulator, thisFunc);
proxy::getDemodState(sid, demodulator, thisFunc);
#ifdef MODEM_IS_TDMA
const auto tmpDevState = proxy::getDmaDebug(sid, "status_init", thisFunc);
#endif
#ifdef MODEM_IS_SCPC
bool isCinC = getIsCinC();
if (isCinC) {
logCpApiError("api_driver::TerminalApiDaemon::updateState()->CP_GetCinCState()", CP_GetCinCState(sid, cinc));
}
#endif
{
std::lock_guard lock2(this->mutex);
#ifdef MODEM_IS_TDMA
this->fInitState = tmpDevState;
#endif
this->fTxState = modulator.is_tx_on;
this->fTxModcod = modulator.modcod;
this->fTxSnr = modulator.snr_remote;
this->fTxframeSizeNormal = modulator.snr_remote;
this->fTx = modulator.snr_remote;
this->fTx = modulator.snr_remote;
this->fTx = modulator.snr_remote;
#ifdef MODEM_IS_SCPC
result << ",\"tx.snr\":"; writeDouble(result, modulator.snr_remote);
if (modulator.is_short) { result << R"(,"tx.frameSizeNormal":false)"; }
else { result << R"(,"tx.frameSizeNormal":true)"; }
if (modulator.is_pilots) { result << R"(,"tx.isPilots":true)"; }
else { result << R"(,"tx.isPilots":false)"; }
#else
{
modulator_settings modSet{};
daemon->getSettings(&modSet, nullptr, nullptr, nullptr);
result << ",\"tx.centerFreq\":"; writeDouble(result, modSet.central_freq_in_kGz);
result << ",\"tx.symSpeed\":"; writeDouble(result, (static_cast<double>(modSet.baudrate) / 1000.0));
}
#endif
/*
result << ",\"tx.speedOnTxKbit\":"; writeDouble(result, static_cast<double>(modulator.speed_in_bytes_tx) / 128.0);
result << ",\"tx.speedOnIifKbit\":"; writeDouble(result, (static_cast<double>(modulator.speed_in_bytes_tx_iface) / 128.0));
// формируем структуру для RX
result << ",\n\"rx.state\":" << boolAsStr(demodulator.locks.sym_sync_lock && demodulator.locks.freq_lock && demodulator.locks.afc_lock && demodulator.locks.pkt_sync);
result << ",\"rx.sym_sync_lock\":" << boolAsStr(demodulator.locks.sym_sync_lock);
result << ",\"rx.freq_search_lock\":" << boolAsStr(demodulator.locks.freq_lock);
result << ",\"rx.afc_lock\":" << boolAsStr(demodulator.locks.afc_lock);
result << ",\"rx.pkt_sync\":" << boolAsStr(demodulator.locks.pkt_sync);
result << ",\"rx.snr\":"; writeDouble(result, demodulator.snr);
result << ",\"rx.rssi\":"; writeDouble(result, demodulator.rssi);
result << ",\"rx.modcod\":" << demodulator.modcod;
if (demodulator.is_short) {
result << R"(,"rx.frameSizeNormal":false)";
} else {
result << R"(,"rx.frameSizeNormal":true)";
}
if (demodulator.is_pilots) {
result << R"(,"rx.isPilots":true)";
} else {
result << R"(,"rx.isPilots":false)";
}
result << ",\n\"rx.symError\":"; writeDouble(result, demodulator.sym_err);
result << ",\"rx.freqErr\":"; writeDouble(result, demodulator.crs_freq_err);
result << ",\"rx.freqErrAcc\":"; writeDouble(result, demodulator.fine_freq_err);
result << ",\"rx.inputSignalLevel\":"; writeDouble(result, demodulator.if_overload);
result << ",\"rx.pllError\":"; writeDouble(result, demodulator.afc_err);
result << ",\"rx.speedOnRxKbit\":"; writeDouble(result, static_cast<double>(demodulator.speed_in_bytes_rx) / 128.0);
result << ",\"rx.speedOnIifKbit\":"; writeDouble(result, static_cast<double>(demodulator.speed_in_bytes_rx_iface) / 128.0);
result << ",\"rx.packetsOk\":" << demodulator.packet_ok_cnt;
result << ",\"rx.packetsBad\":" << demodulator.packet_bad_cnt;
result << ",\"rx.packetsDummy\":" << demodulator.dummy_cnt;
#ifdef MODEM_IS_SCPC
// формируем структуру для CinC
if (isCinC) {
if (modulator.is_tx_on) {
if (cinc.carrier_lock) {
result << R"(,"cinc.correlator":true)";
} else {
result << R"(,"cinc.correlator":false)";
}
} else {
result << R"(,"cinc.correlator":null)";
}
result << ",\n\"cinc.occ\":"; writeDouble(result, cinc.ratio_signal_signal, 3);
result << ",\"cinc.correlatorFails\":" << cinc.cnt_bad_lock;
result << ",\"cinc.freqErr\":" << cinc.freq_error_offset;
result << ",\"cinc.freqErrAcc\":" << cinc.freq_fine_estimate;
result << ",\"cinc.channelDelay\":" << cinc.delay_dpdi;
} else {
result << R"(,"cinc.correlator":null)";
}
#endif
// структура температур девайса
result << ",\n\"device.adrv\":"; writeDouble(result, device.adrv_temp, 1);
result << ",\"device.fpga\":"; writeDouble(result, device.pl_temp, 1);
result << ",\"device.zynq\":"; writeDouble(result, device.zynq_temp, 1);
#ifdef MODEM_IS_TDMA
if (device.cur_image.empty()) {
result << R"(,
"device.upgradeStatus":"Нет обновлений","device.upgradePercent":0,"device.upgradeImage":"")";
} else {
switch (device.status) {
case NORM_RX_OBJECT_NEW_API: result << ",\n" R"("device.upgradeStatus": "Начало загрузки")"; break;
case NORM_RX_OBJECT_INFO_API: result << ",\n" R"("device.upgradeStatus": "Получено имя образа")"; break;
case NORM_RX_OBJECT_UPDATED_API: result << ",\n" R"("device.upgradeStatus": "Загружается")"; break;
case NORM_RX_OBJECT_COMPLETED_API: result << ",\n" R"("device.upgradeStatus": "Загрузка завершена")"; break;
case NORM_RX_OBJECT_ABORTED_API: result << ",\n" R"("device.upgradeStatus": "Загрузка прервана")"; break;
default: result << ",\n" R"("device.upgradeStatus": "?")";
}
result << ",\"device.upgradePercent\":" << device.dwl_percent;
result << ",\"device.upgradeImage\":" << buildEscapedString(device.cur_image);
}
#endif
result << "}";
*/
this->modState = modulator;
this->demodState = demodulator;
#ifdef MODEM_IS_SCPC
this->cincState = cinc;
#endif
}
}
api_driver::obj::TerminalState::~TerminalState() = default;

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

@ -0,0 +1,279 @@
#ifndef STRUCTS_H
#define STRUCTS_H
#include <shared_mutex>
#include <string>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include <terminal_api/ControlProtoCInterface.h>
namespace api_driver::obj {
/**
* Обертка для объектов, доступных для обновления
* NOTE: перед вызовом функций, требующих `TSID`, необходимо захватить мютекс API.
*/
class CpUpdatebleObject {
public:
int64_t lastUpdate = 0;
int64_t updatePeriodMs = -1;
virtual void updateCallback(TSID sid, CP_Result& lastCpError) = 0;
bool checkNeedUpdate(int64_t now) const;
int64_t getNextUpdate(int64_t now) const;
virtual ~CpUpdatebleObject();
};
#ifdef MODEM_IS_SCPC
class api_driver::StatisticsLogger {
public:
StatisticsLogger(): timeStart(TIME_NOW()) {}
int64_t timeStart;
bool logEn = false;
std::atomic<int> logPeriodMs = 1000;
std::atomic<int> maxAgeMs = 10000;
/**
*
* @return {"en": bool, "logPeriodMs": int, "maxAgeMs": int}
*/
std::string getSettings() {
std::lock_guard _lock(mutex);
std::stringstream res;
res << "{\"en\":" << boolAsStr(this->logEn);
res << ",\"logPeriodMs\":" << logPeriodMs;
res << ",\"maxAgeMs\":" << maxAgeMs;
res << '}';
return res.str();
}
void setSettings(boost::property_tree::ptree &pt) {
const bool newEn = pt.get<bool>("en");
const int newInterval = pt.get<int>("logPeriodMs");
const int newMaxAgeMs = pt.get<int>("maxAgeMs");
std::lock_guard _lock(this->mutex);
this->logPeriodMs = newInterval;
this->maxAgeMs = newMaxAgeMs;
if (newEn != this->logEn) {
if (newEn) {
this->logFile.open("/tmp/weblog-statistics.csv", 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;
}
}
}
/**
* Записать значение в "базу данных". Метку при этом вставлять не нужно, она будет вставлена автоматически.
* @param item
*/
void 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();
}
}
// void collectExpiredItems();
// void resetLogs() {
// std::lock_guard _lock(mutex);
// logs.clear();
// }
~StatisticsLogger() = default;
private:
// std::pmr::deque<LogItem> logs;
std::fstream logFile{};
std::shared_mutex mutex;
};
#endif
#ifdef MODEM_IS_SCPC
static constexpr const char* DEFAULT_SERVER_NAME = "RCSM-101";
#endif
#ifdef MODEM_IS_TDMA
static constexpr const char* DEFAULT_SERVER_NAME = "RCSM-101 TDMA";
#endif
#if defined(MODEM_IS_SCPC)
class TerminalNetworkSettings: public CpUpdatebleObject {
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() {
managementIp = "0.0.0.0";
managementGateway = "";
isL2 = true;
dataIp = "0.0.0.0";
dataMtu = 1500;
serverName = DEFAULT_SERVER_NAME;
}
void updateCallback(TSID sid, CP_Result &lastCpError) override;
void updateFromPt(boost::property_tree::ptree &pt);
void store(TSID sid, CP_Result& lastCpError);
std::string asJson();
~TerminalNetworkSettings() override;
};
#endif
class TerminalFirmwareVersion {
public:
std::string version, modemId, modemSn, macMang, macData;
TerminalFirmwareVersion();
TerminalFirmwareVersion(const TerminalFirmwareVersion& src);
~TerminalFirmwareVersion();
std::string asJson();
TerminalFirmwareVersion& operator= (const TerminalFirmwareVersion& src);
};
/**
* Обертка состояния терминала, тут состояние девайса, модулятора/демодулятора, состояние автоматического обновления.
*/
class TerminalState: public CpUpdatebleObject {
public:
std::string fInitState{};
bool fIsTest = false; // daemon->isTest()
bool fTxState = false;
#ifdef MODEM_IS_SCPC
bool fIsCinC = false;
#endif
bool fStatRxState;
bool fStatRxSymSyncLock;
bool fStatRxFreqSearchLock;
bool fStatRxAfcLock;
bool fStatRxPktSync;
float fStatRxSnr;
float fStatRxRssi;
uint16_t fStatRxModcod;
bool fStatRxFrameSizeNormal;
bool fStatRxIsPilots;
double fStatRxSymError;
double fStatRxFreqErr;
double fStatRxFreqErrAcc;
double fStatRxInputSignalLevel;
double fStatRxPllError;
double fStatRxSpeedOnRxKbit;
double fStatRxSpeedOnIifKbit;
uint32_t fStatRxPacketsOk;
uint32_t fStatRxPacketsBad;
uint32_t fStatRxPacketsDummy;
bool fStatTxState;
uint16_t fStatTxModcod;
double fStatTxSpeedOnTxKbit;
double fStatTxSpeedOnIifKbit;
#ifdef MODEM_IS_SCPC
float fStatTxSnr;
bool fStatTxFrameSizeNormal;
bool fStatTxIsPilots;
double fStatCincOcc;
bool fStatCincCorrelator;
uint32_t fStatCincCorrelatorFails;
int32_t fStatCincFreqErr;
int32_t fStatCincFreqErrAcc;
float fStatCincChannelDelay;
#endif
#ifdef MODEM_IS_TDMA
fStatTxCenterFreq;
fStatTxSymSpeed;
#endif
/**
* Обновление основной части статистики, то есть RX/TX и sysinfo
*/
void updateCallback(TSID sid, CP_Result &lastCpError) override;
void updateFromPt(boost::property_tree::ptree &pt);
void store(TSID sid, CP_Result& lastCpError);
std::string asJson();
~TerminalState() override;
};
class TerminalDeviceState: public CpUpdatebleObject {
public:
time_t fStatOsUptime;
double fStatOsLoad1;
double fStatOsLoad5;
double fStatOsLoad15;
int fStatOsTotalram;
int fStatOsFreeram;
int fStatOsProcs;
double fStatDeviceAdrv;
double fStatDeviceZynq;
double fStatDeviceFpga;
#ifdef MODEM_IS_TDMA
DOWNLOAD_STATUS fStatDeviceUpgradeStatus;
unsigned int fStatDeviceUpgradePercent;
std::string fStatDeviceUpgradeImage;
#endif
void updateCallback(TSID sid, CP_Result &lastCpError) override;
void updateFromPt(boost::property_tree::ptree &pt);
void store(TSID sid, CP_Result& lastCpError);
std::string asJson();
~TerminalDeviceState() override;
};
}
#endif //STRUCTS_H

View File

@ -1,7 +1,6 @@
#ifndef TERMINAL_API_DRIVER_H #ifndef TERMINAL_API_DRIVER_H
#define TERMINAL_API_DRIVER_H #define TERMINAL_API_DRIVER_H
#include <deque>
#include <memory> #include <memory>
#include <string> #include <string>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>

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

@ -0,0 +1,764 @@
<!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>Захват символьной</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>
</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>
<select v-model="paramRxtx.txIsTestInput">
<option :value="false">Ethernet</option>
<option :value="true">Тест</option>
</select>
</label>
<h3>Параметры передачи</h3>
<label><span>Центральная частота, КГц</span><input type="number" v-model="paramRxtx.txCentralFreq" min="950000" max="6000000" step="0.01"/></label>
<label><span>Символьная скорость, Бод</span><input type="number" v-model="paramRxtx.txBaudrate" min="200000" max="54000000"/></label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.txRolloff">
<option :value="2">0.02</option>
<option :value="5">0.05</option>
<option :value="10">0.10</option>
<option :value="15">0.15</option>
<option :value="20">0.20</option>
<option :value="25">0.25</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.txSpreadCoef" min="-1000" max="1000" step="0.01"/></label>
<label>
<span>Номер последовательности Голда</span>
<select v-model="paramRxtx.txGoldan">
<option :value="0">0</option>
<option :value="1">1</option>
</select>
</label>
<label><span>Ослабление, дБ</span><input type="number" v-model="paramRxtx.txAttenuation" min="-40" step="0.25"/></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="number" v-model="paramRxtx.rxCentralFreq" min="950000" max="6000000" step="0.01"/></label>
<label><span>Символьная скорость, Бод</span><input type="number" v-model="paramRxtx.rxBaudrate" min="200000" max="54000000"/></label>
<label>
<span>Roll-off</span>
<select v-model="paramRxtx.rxRolloff">
<option :value="2">0.02</option>
<option :value="5">0.05</option>
<option :value="10">0.10</option>
<option :value="15">0.15</option>
<option :value="20">0.20</option>
<option :value="25">0.25</option>
</select>
</label>
<label><span>Коэф. расширения</span><input type="number" v-model="paramRxtx.rxSpreadCoef" min="-1000" max="1000" step="0.01"/></label>
<label>
<span>Номер последовательности Голда</span>
<select v-model="paramRxtx.rxGoldan">
<option :value="0">0</option>
<option :value="1">1</option>
</select>
</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 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>
</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,
firmwareUpload: false,
firmwareUpgrade: false,
// когда модем перезагружается, тут должен быть счетчик. Направление счета - к нулю
modemReboot: null
},
// ========== include from 'common/all-params-data.js.j2'
paramRxtx: {
txEn: false,
txAutoStart: false,
txModulatorIsTest: false,
txIsTestInput: false,
txCentralFreq: 950000,
txBaudrate: 200000,
txRolloff: 2,
txSpreadCoef: -1000,
txGoldan: 0,
txAttenuation: -40,
rxAgcEn: false,
rxManualGain: -40,
rxSpectrumInversion: false,
rxCentralFreq: 950000,
rxBaudrate: 200000,
rxRolloff: 2,
rxSpreadCoef: -1000,
rxGoldan: 0,
},
paramBuclnb: {
bucRefClk10M: false,
bucPowering: 0,
lnbRefClk10M: false,
lnbPowering: 0,
srvRefClk10M: false,
bucLnbAutoStart: false,
},
// ========== 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,
"txIsTestInput": this.paramRxtx.txIsTestInput,
"txCentralFreq": this.paramRxtx.txCentralFreq,
"txBaudrate": this.paramRxtx.txBaudrate,
"txRolloff": this.paramRxtx.txRolloff,
"txSpreadCoef": this.paramRxtx.txSpreadCoef,
"txGoldan": this.paramRxtx.txGoldan,
"txAttenuation": this.paramRxtx.txAttenuation,
"rxAgcEn": this.paramRxtx.rxAgcEn,
"rxManualGain": this.paramRxtx.rxManualGain,
"rxSpectrumInversion": this.paramRxtx.rxSpectrumInversion,
"rxCentralFreq": this.paramRxtx.rxCentralFreq,
"rxBaudrate": this.paramRxtx.rxBaudrate,
"rxRolloff": this.paramRxtx.rxRolloff,
"rxSpreadCoef": this.paramRxtx.rxSpreadCoef,
"rxGoldan": this.paramRxtx.rxGoldan,
}
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 })
},
updateRxtxSettings(vals) {
this.submitStatus.rxtx = false
this.paramRxtx.txEn = vals["settings"]["txEn"]
this.paramRxtx.txAutoStart = vals["settings"]["txAutoStart"]
this.paramRxtx.txModulatorIsTest = vals["settings"]["txModulatorIsTest"]
this.paramRxtx.txIsTestInput = vals["settings"]["txIsTestInput"]
this.paramRxtx.txCentralFreq = vals["settings"]["txCentralFreq"]
this.paramRxtx.txBaudrate = vals["settings"]["txBaudrate"]
this.paramRxtx.txRolloff = vals["settings"]["txRolloff"]
this.paramRxtx.txSpreadCoef = vals["settings"]["txSpreadCoef"]
this.paramRxtx.txGoldan = vals["settings"]["txGoldan"]
this.paramRxtx.txAttenuation = vals["settings"]["txAttenuation"]
this.paramRxtx.rxAgcEn = vals["settings"]["rxAgcEn"]
this.paramRxtx.rxManualGain = vals["settings"]["rxManualGain"]
this.paramRxtx.rxSpectrumInversion = vals["settings"]["rxSpectrumInversion"]
this.paramRxtx.rxCentralFreq = vals["settings"]["rxCentralFreq"]
this.paramRxtx.rxBaudrate = vals["settings"]["rxBaudrate"]
this.paramRxtx.rxRolloff = vals["settings"]["rxRolloff"]
this.paramRxtx.rxSpreadCoef = vals["settings"]["rxSpreadCoef"]
this.paramRxtx.rxGoldan = vals["settings"]["rxGoldan"]
},
updateBuclnbSettings(vals) {
this.submitStatus.buclnb = false
this.paramBuclnb.bucRefClk10M = vals["settings"]["bucRefClk10M"]
this.paramBuclnb.bucPowering = vals["settings"]["bucPowering"]
this.paramBuclnb.lnbRefClk10M = vals["settings"]["lnbRefClk10M"]
this.paramBuclnb.lnbPowering = vals["settings"]["lnbPowering"]
this.paramBuclnb.srvRefClk10M = vals["settings"]["srvRefClk10M"]
this.paramBuclnb.bucLnbAutoStart = vals["settings"]["bucLnbAutoStart"]
},
// ========== 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["mainState"]["initState"]
this.statRx.state = vals["mainState"]["rx.state"]
this.statRx.sym_sync_lock = vals["mainState"]["rx.sym_sync_lock"]
this.statRx.freq_search_lock = vals["mainState"]["rx.freq_search_lock"]
this.statRx.afc_lock = vals["mainState"]["rx.afc_lock"]
this.statRx.pkt_sync = vals["mainState"]["rx.pkt_sync"]
this.statRx.snr = vals["mainState"]["rx.snr"]
this.statRx.rssi = vals["mainState"]["rx.rssi"]
this.statRx.modcod = modcodToStr(vals["mainState"]["rx.modcod"])
this.statRx.frameSizeNormal = vals["mainState"]["rx.frameSizeNormal"]
this.statRx.isPilots = vals["mainState"]["rx.isPilots"]
this.statRx.symError = vals["mainState"]["rx.symError"]
this.statRx.freqErr = vals["mainState"]["rx.freqErr"]
this.statRx.freqErrAcc = vals["mainState"]["rx.freqErrAcc"]
this.statRx.inputSignalLevel = vals["mainState"]["rx.inputSignalLevel"]
this.statRx.pllError = vals["mainState"]["rx.pllError"]
this.statRx.speedOnRxKbit = vals["mainState"]["rx.speedOnRxKbit"]
this.statRx.speedOnIifKbit = vals["mainState"]["rx.speedOnIifKbit"]
this.statRx.packetsOk = vals["mainState"]["rx.packetsOk"]
this.statRx.packetsBad = vals["mainState"]["rx.packetsBad"]
this.statRx.packetsDummy = vals["mainState"]["rx.packetsDummy"]
this.statTx.state = vals["mainState"]["tx.state"]
this.statTx.modcod = modcodToStr(vals["mainState"]["tx.modcod"])
this.statTx.speedOnTxKbit = vals["mainState"]["tx.speedOnTxKbit"]
this.statTx.speedOnIifKbit = vals["mainState"]["tx.speedOnIifKbit"]
this.statTx.centerFreq = vals["mainState"]["tx.centerFreq"]
this.statTx.symSpeed = vals["mainState"]["tx.symSpeed"]
this.statDevice.adrv = vals["mainState"]["device.adrv"]
this.statDevice.zynq = vals["mainState"]["device.zynq"]
this.statDevice.fpga = vals["mainState"]["device.fpga"]
this.testState = vals["mainState"]["testState"]
// аптайм приходит в секундах, надо преобразовать его в человеко-читаемый вид
let uptime = vals["sysinfo"]["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 = vals["sysinfo"]["load1min"]
this.statOs.load5 = vals["sysinfo"]["load5min"]
this.statOs.load15 = vals["sysinfo"]["load15min"]
this.statOs.totalram = vals["sysinfo"]["totalram"]
this.statOs.freeram = vals["sysinfo"]["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) => {})
}, // ========== 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)
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["fw.version"]
this.about.modemUid = d["fw.modemId"]
this.about.modemSn = d["fw.modemSn"]
this.about.macManagement = d["fw.macMang"]
this.about.macData = d["fw.macData"]
} catch (e) {
console.log('Ошибка загрузки версии ПО', e)
}
}
doFetchStatistics().then(() => {})
doFetchAbout().then(() => {})
this.performUpdateSettings()
document.getElementById("app").removeAttribute("hidden")
}
});
app.mount('#app')
</script>
</body>
</html>