add code dump of private sysview FNE monitoring utility to public GPL; correct issue with large REST responses getting truncated;
parent
555ec97c90
commit
fe6f7508c0
@ -0,0 +1,61 @@
|
||||
#
|
||||
# Digital Voice Modem - FNE System View
|
||||
#
|
||||
|
||||
#
|
||||
# Radio ID ACL Configuration
|
||||
#
|
||||
radio_id:
|
||||
# Full path to the Radio ID ACL file.
|
||||
file: rid_acl.dat
|
||||
# Amount of time between updates of Radio ID ACL file. (minutes)
|
||||
time: 2
|
||||
|
||||
#
|
||||
# Talkgroup Rules Configuration
|
||||
#
|
||||
talkgroup_rules:
|
||||
# Full path to the talkgroup rules file.
|
||||
file: talkgroup_rules.yml
|
||||
# Amount of time between updates of talkgroup rules file. (minutes)
|
||||
time: 30
|
||||
|
||||
#
|
||||
# Channel Identity Table Configuration
|
||||
#
|
||||
iden_table:
|
||||
# Full path to the identity table file.
|
||||
file: iden_table.dat
|
||||
# Amount of time between updates of identity table file. (minutes)
|
||||
time: 30
|
||||
|
||||
#
|
||||
# FNE Configuration
|
||||
#
|
||||
fne:
|
||||
# Hostname/IP address of the FNE master to connect to.
|
||||
masterAddress: 127.0.0.1
|
||||
# Port number of the FNE master to connect to.
|
||||
masterPort: 32090
|
||||
# FNE access password.
|
||||
password: RPT1234
|
||||
# Textual identity of this peer.
|
||||
identity: EXPEER
|
||||
# Network Peer ID
|
||||
peerId: 9000990
|
||||
|
||||
# Flag indicating whether or not peer endpoint networking is encrypted.
|
||||
encrypted: false
|
||||
# AES-256 32-byte Preshared Key
|
||||
# (This field *must* be 32 hex bytes in length or 64 characters
|
||||
# 0 - 9, A - F.)
|
||||
presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
|
||||
|
||||
# IP address of the FNE REST API.
|
||||
restAddress: 127.0.0.1
|
||||
# Port number for the FNE REST API.
|
||||
restPort: 9990
|
||||
# Flag indicating whether or not REST API is operating in SSL mode.
|
||||
restSsl: false
|
||||
# REST API authentication password.
|
||||
restPassword: "PASSWORD"
|
||||
@ -0,0 +1,243 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file AffListWnd.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__AFF_LIST_WND_H__)
|
||||
#define __AFF_LIST_WND_H__
|
||||
|
||||
#include "common/lookups/AffiliationLookup.h"
|
||||
#include "common/Log.h"
|
||||
#include "fne/network/RESTDefines.h"
|
||||
#include "remote/RESTClient.h"
|
||||
|
||||
#include "SysViewMainWnd.h"
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define AFF_LIST_WIDTH 74
|
||||
#define AFF_LIST_HEIGHT 15
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements the affiliations list window.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API AffListWnd final : public finalcut::FDialog {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the AffListWnd class.
|
||||
* @param widget
|
||||
*/
|
||||
explicit AffListWnd(FWidget* widget = nullptr) : FDialog{widget}
|
||||
{
|
||||
m_timerId = addTimer(10000); // starts the timer every 10 seconds
|
||||
}
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
AffListWnd(const AffListWnd&) = delete;
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
AffListWnd(AffListWnd&&) noexcept = delete;
|
||||
/**
|
||||
* @brief Finalizes an instance of the AffListWnd class.
|
||||
*/
|
||||
~AffListWnd() noexcept override = default;
|
||||
|
||||
/**
|
||||
* @brief Disable copy assignment operator (=).
|
||||
*/
|
||||
auto operator= (const AffListWnd&) -> AffListWnd& = delete;
|
||||
/**
|
||||
* @brief Disable move assignment operator (=).
|
||||
*/
|
||||
auto operator= (AffListWnd&&) noexcept -> AffListWnd& = delete;
|
||||
|
||||
/**
|
||||
* @brief Disable set X coordinate.
|
||||
*/
|
||||
void setX(int, bool = true) override { }
|
||||
/**
|
||||
* @brief Disable set Y coordinate.
|
||||
*/
|
||||
void setY(int, bool = true) override { }
|
||||
/**
|
||||
* @brief Disable set position.
|
||||
*/
|
||||
void setPos(const FPoint&, bool = true) override { }
|
||||
|
||||
/**
|
||||
* @brief Populates the talkgroup listview.
|
||||
*/
|
||||
void loadListView()
|
||||
{
|
||||
yaml::Node fne = g_conf["fne"];
|
||||
std::string fneRESTAddress = fne["restAddress"].as<std::string>("127.0.0.1");
|
||||
uint16_t fneRESTPort = (uint16_t)fne["restPort"].as<uint32_t>(9990U);
|
||||
std::string fnePassword = fne["restPassword"].as<std::string>("PASSWORD");
|
||||
bool fneSSL = fne["restSsl"].as<bool>(false);
|
||||
|
||||
// callback REST API to get status of the channel we represent
|
||||
json::object req = json::object();
|
||||
json::object rsp = json::object();
|
||||
|
||||
int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword,
|
||||
HTTP_GET, FNE_GET_AFF_LIST, req, rsp, fneSSL, g_debug);
|
||||
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
|
||||
::LogError(LOG_HOST, "[AFFVIEW] failed to get affiliations for %s:%u", fneRESTAddress.c_str(), fneRESTPort);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
m_listView.clear();
|
||||
|
||||
json::array fneAffils = rsp["affiliations"].get<json::array>();
|
||||
for (auto entry : fneAffils) {
|
||||
json::object peerAffils = entry.get<json::object>();
|
||||
uint32_t peerId = peerAffils["peerId"].get<uint32_t>();
|
||||
json::array affils = peerAffils["affiliations"].get<json::array>();
|
||||
for (auto pentry : affils) {
|
||||
json::object peerEntry = pentry.get<json::object>();
|
||||
uint32_t dstId = peerEntry["dstId"].get<uint32_t>();
|
||||
uint32_t srcId = peerEntry["srcId"].get<uint32_t>();
|
||||
|
||||
// pad peer IDs properly
|
||||
std::ostringstream peerOss;
|
||||
peerOss << std::setw(9) << std::setfill('0') << peerId;
|
||||
|
||||
// pad TGs properly
|
||||
std::ostringstream tgidOss;
|
||||
tgidOss << std::setw(5) << std::setfill('0') << dstId;
|
||||
|
||||
// build list view entry
|
||||
const std::array<std::string, 3U> columns = {
|
||||
peerOss.str(),
|
||||
std::to_string(srcId),
|
||||
tgidOss.str()
|
||||
};
|
||||
|
||||
const finalcut::FStringList line(columns.cbegin(), columns.cend());
|
||||
m_listView.insert(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle affiliation request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
setFocusWidget(&m_listView);
|
||||
redraw();
|
||||
}
|
||||
|
||||
private:
|
||||
int m_timerId;
|
||||
|
||||
FListView m_listView{this};
|
||||
|
||||
FButton m_refresh{"&Refresh", this};
|
||||
|
||||
/**
|
||||
* @brief Initializes the window layout.
|
||||
*/
|
||||
void initLayout() override
|
||||
{
|
||||
FDialog::setMinimumSize(FSize{AFF_LIST_WIDTH, AFF_LIST_HEIGHT});
|
||||
|
||||
FDialog::setResizeable(false);
|
||||
FDialog::setMinimizable(false);
|
||||
FDialog::setTitlebarButtonVisibility(false);
|
||||
FDialog::setModal(false);
|
||||
|
||||
FDialog::setText("Affiliations View (10s)");
|
||||
|
||||
initControls();
|
||||
loadListView();
|
||||
|
||||
FDialog::initLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes window controls.
|
||||
*/
|
||||
void initControls()
|
||||
{
|
||||
m_refresh.setGeometry(FPoint(int(getWidth()) - 12, 1), FSize(9, 1));
|
||||
m_refresh.addCallback("clicked", [&]() { loadListView(); });
|
||||
|
||||
m_listView.setGeometry(FPoint{1, 3}, FSize{getWidth() - 1, getHeight() - 5});
|
||||
|
||||
// configure list view columns
|
||||
m_listView.addColumn("Peer ID", 10);
|
||||
m_listView.addColumn("RID", 10);
|
||||
m_listView.addColumn("TGID", 9);
|
||||
|
||||
// set right alignment for TGID
|
||||
m_listView.setColumnAlignment(1, finalcut::Align::Right);
|
||||
m_listView.setColumnAlignment(2, finalcut::Align::Right);
|
||||
m_listView.setColumnAlignment(3, finalcut::Align::Right);
|
||||
|
||||
// set type of sorting
|
||||
m_listView.setColumnSortType(1, finalcut::SortType::Name);
|
||||
m_listView.setColumnSortType(2, finalcut::SortType::Name);
|
||||
m_listView.setColumnSortType(3, finalcut::SortType::Name);
|
||||
|
||||
// sort by TGID
|
||||
m_listView.setColumnSort(1, finalcut::SortOrder::Ascending);
|
||||
m_listView.setColumnSort(3, finalcut::SortOrder::Ascending);
|
||||
|
||||
setFocusWidget(&m_listView);
|
||||
redraw();
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Event that occurs on keyboard key press.
|
||||
* @param e Keyboard Event.
|
||||
*/
|
||||
void onKeyPress(finalcut::FKeyEvent* e) override
|
||||
{
|
||||
const auto key = e->key();
|
||||
if (key == FKey::F5) {
|
||||
loadListView();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event that occurs on interval by timer.
|
||||
* @param timer Timer Event
|
||||
*/
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
// update timer
|
||||
if (timer->getTimerId() == m_timerId) {
|
||||
loadListView();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __AFF_LIST_WND_H__
|
||||
@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#/*
|
||||
# * Digital Voice Modem - FNE System View
|
||||
# * GPLv2 Open Source. Use is subject to license terms.
|
||||
# * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
# *
|
||||
# * Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
# *
|
||||
# */
|
||||
file(GLOB sysView_SRC
|
||||
"src/host/modem/Modem.h"
|
||||
"src/host/network/Network.h"
|
||||
"src/host/network/Network.cpp"
|
||||
|
||||
"src/remote/RESTClient.cpp"
|
||||
"src/remote/RESTClient.h"
|
||||
|
||||
"src/sysview/network/*.h"
|
||||
"src/sysview/network/*.cpp"
|
||||
"src/sysview/*.h"
|
||||
"src/sysview/*.cpp"
|
||||
)
|
||||
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @defgroup fneSysView FNE System View (dvmmon)
|
||||
* @brief Digital Voice Modem - FNE System View
|
||||
* @details Monitor software that connects to the DVM FNE and is a quick TUI for monitoring affiliations on them.
|
||||
* @ingroup fneSysView
|
||||
*
|
||||
* @file Defines.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__DEFINES_H__)
|
||||
#define __DEFINES_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#undef __PROG_NAME__
|
||||
#define __PROG_NAME__ "Digital Voice Modem (DVM) FNE System View"
|
||||
#undef __EXE_NAME__
|
||||
#define __EXE_NAME__ "sysview"
|
||||
|
||||
#endif // __DEFINES_H__
|
||||
@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file LogDisplayWnd.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__LOG_DISPLAY_WND_H__)
|
||||
#define __LOG_DISPLAY_WND_H__
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements the log display window.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the LogDisplayWnd class.
|
||||
* @param widget
|
||||
*/
|
||||
explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget}
|
||||
{
|
||||
m_scrollText.ignorePadding();
|
||||
|
||||
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
|
||||
}
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
LogDisplayWnd(const LogDisplayWnd&) = delete;
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
LogDisplayWnd(LogDisplayWnd&&) noexcept = delete;
|
||||
/**
|
||||
* @brief Finalizes an instance of the LogDisplayWnd class.
|
||||
*/
|
||||
~LogDisplayWnd() noexcept override = default;
|
||||
|
||||
/**
|
||||
* @brief Disable copy assignment operator (=).
|
||||
*/
|
||||
auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete;
|
||||
/**
|
||||
* @brief Disable move assignment operator (=).
|
||||
*/
|
||||
auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete;
|
||||
|
||||
private:
|
||||
FTextView m_scrollText{this};
|
||||
int m_timerId;
|
||||
|
||||
/**
|
||||
* @brief Initializes the window layout.
|
||||
*/
|
||||
void initLayout() override
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
auto lightning = "\u26a1";
|
||||
FDialog::setText("System Log"s + lightning);
|
||||
|
||||
std::size_t maxWidth;
|
||||
const auto& rootWidget = getRootWidget();
|
||||
|
||||
if (rootWidget) {
|
||||
maxWidth = rootWidget->getClientWidth() - 3;
|
||||
}
|
||||
else {
|
||||
// fallback to xterm default size
|
||||
maxWidth = 77;
|
||||
}
|
||||
|
||||
FDialog::setGeometry(FPoint{2, (int)(rootWidget->getClientHeight() - 40)}, FSize{maxWidth, 40});
|
||||
FDialog::setMinimumSize(FSize{80, 20});
|
||||
FDialog::setResizeable(true);
|
||||
FDialog::setMinimizable(true);
|
||||
FDialog::setTitlebarButtonVisibility(true);
|
||||
FDialog::setShadow();
|
||||
|
||||
minimizeWindow();
|
||||
|
||||
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
|
||||
|
||||
FDialog::initLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adjusts window size.
|
||||
*/
|
||||
void adjustSize() override
|
||||
{
|
||||
FDialog::adjustSize();
|
||||
|
||||
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Event that occurs when the window is closed.
|
||||
* @param e Close Event
|
||||
*/
|
||||
void onClose(FCloseEvent* e) override
|
||||
{
|
||||
minimizeWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Event that occurs on interval by timer.
|
||||
* @param timer Timer Event
|
||||
*/
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
if (timer->getTimerId() == m_timerId) {
|
||||
if (str().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scrollText.append(str());
|
||||
str("");
|
||||
m_scrollText.scrollToEnd();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __LOG_DISPLAY_WND_H__
|
||||
@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file PeerListWnd.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__PEER_LIST_WND_H__)
|
||||
#define __PEER_LIST_WND_H__
|
||||
|
||||
#include "common/lookups/AffiliationLookup.h"
|
||||
#include "common/Log.h"
|
||||
#include "fne/network/RESTDefines.h"
|
||||
#include "remote/RESTClient.h"
|
||||
|
||||
#include "SysViewMainWnd.h"
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define PEER_LIST_WIDTH 56
|
||||
#define PEER_LIST_HEIGHT 15
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements the peer list window.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API PeerListWnd final : public finalcut::FDialog {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the PeerListWnd class.
|
||||
* @param widget
|
||||
*/
|
||||
explicit PeerListWnd(FWidget* widget = nullptr) : FDialog{widget}
|
||||
{
|
||||
m_timerId = addTimer(25000); // starts the timer every 25 seconds
|
||||
}
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
PeerListWnd(const PeerListWnd&) = delete;
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
PeerListWnd(PeerListWnd&&) noexcept = delete;
|
||||
/**
|
||||
* @brief Finalizes an instance of the PeerListWnd class.
|
||||
*/
|
||||
~PeerListWnd() noexcept override = default;
|
||||
|
||||
/**
|
||||
* @brief Disable copy assignment operator (=).
|
||||
*/
|
||||
auto operator= (const PeerListWnd&) -> PeerListWnd& = delete;
|
||||
/**
|
||||
* @brief Disable move assignment operator (=).
|
||||
*/
|
||||
auto operator= (PeerListWnd&&) noexcept -> PeerListWnd& = delete;
|
||||
|
||||
/**
|
||||
* @brief Disable set X coordinate.
|
||||
*/
|
||||
void setX(int, bool = true) override { }
|
||||
/**
|
||||
* @brief Disable set Y coordinate.
|
||||
*/
|
||||
void setY(int, bool = true) override { }
|
||||
/**
|
||||
* @brief Disable set position.
|
||||
*/
|
||||
void setPos(const FPoint&, bool = true) override { }
|
||||
|
||||
/**
|
||||
* @brief Populates the talkgroup listview.
|
||||
*/
|
||||
void loadListView()
|
||||
{
|
||||
yaml::Node fne = g_conf["fne"];
|
||||
std::string fneRESTAddress = fne["restAddress"].as<std::string>("127.0.0.1");
|
||||
uint16_t fneRESTPort = (uint16_t)fne["restPort"].as<uint32_t>(9990U);
|
||||
std::string fnePassword = fne["restPassword"].as<std::string>("PASSWORD");
|
||||
bool fneSSL = fne["restSsl"].as<bool>(false);
|
||||
|
||||
// callback REST API to get status of the channel we represent
|
||||
json::object req = json::object();
|
||||
json::object rsp = json::object();
|
||||
|
||||
int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword,
|
||||
HTTP_GET, FNE_GET_PEER_QUERY, req, rsp, fneSSL, g_debug);
|
||||
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
|
||||
::LogError(LOG_HOST, "[AFFVIEW] failed to query peers for %s:%u", fneRESTAddress.c_str(), fneRESTPort);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
m_listView.clear();
|
||||
|
||||
json::array fnePeers = rsp["peers"].get<json::array>();
|
||||
for (auto entry : fnePeers) {
|
||||
json::object peerObj = entry.get<json::object>();
|
||||
uint32_t peerId = peerObj["peerId"].get<uint32_t>();
|
||||
std::string peerAddress = peerObj["address"].get<std::string>();
|
||||
uint16_t port = (uint16_t)peerObj["port"].get<uint32_t>();
|
||||
bool connected = peerObj["connected"].get<bool>();
|
||||
uint32_t connectionState = (uint32_t)peerObj["connectionState"].get<uint32_t>();
|
||||
std::string strConnState = "";
|
||||
switch (connectionState) {
|
||||
case network::NET_CONN_STATUS::NET_STAT_RUNNING:
|
||||
strConnState = "Connected";
|
||||
break;
|
||||
case network::NET_CONN_STATUS::NET_STAT_WAITING_LOGIN:
|
||||
strConnState = "Waiting for Login";
|
||||
break;
|
||||
case network::NET_CONN_STATUS::NET_STAT_WAITING_AUTHORISATION:
|
||||
strConnState = "Waiting for Auth";
|
||||
break;
|
||||
case network::NET_CONN_STATUS::NET_STAT_WAITING_CONFIG:
|
||||
strConnState = "Waiting for Config";
|
||||
break;
|
||||
default:
|
||||
strConnState = " ?? ";
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t pingsReceived = (uint32_t)peerObj["pingsReceived"].get<uint32_t>();
|
||||
uint64_t lastPing = (uint64_t)peerObj["lastPing"].get<uint32_t>();
|
||||
|
||||
uint32_t ccPeerId = (uint32_t)peerObj["controlChannel"].get<uint32_t>();
|
||||
|
||||
json::array voiceChannels = peerObj["voiceChannels"].get<json::array>();
|
||||
std::vector<uint32_t> voiceChannelPeers;
|
||||
for (auto vcEntry : voiceChannels) {
|
||||
uint32_t vcPeerId = vcEntry.get<uint32_t>();
|
||||
voiceChannelPeers.push_back(vcPeerId);
|
||||
}
|
||||
|
||||
// pad peer IDs properly
|
||||
std::ostringstream peerOss;
|
||||
peerOss << std::setw(9) << std::setfill('0') << peerId;
|
||||
|
||||
// pad peer IDs properly
|
||||
std::ostringstream ccPeerOss;
|
||||
ccPeerOss << std::setw(9) << std::setfill('0') << ccPeerId;
|
||||
|
||||
// build list view entry
|
||||
const std::array<std::string, 8U> columns = {
|
||||
peerOss.str(),
|
||||
peerAddress, std::to_string(port),
|
||||
ccPeerOss.str(),
|
||||
(voiceChannelPeers.size() > 0U) ? "X" : "",
|
||||
(connected) ? "X" : "",
|
||||
strConnState,
|
||||
std::to_string(pingsReceived)
|
||||
};
|
||||
|
||||
const finalcut::FStringList line(columns.cbegin(), columns.cend());
|
||||
m_listView.insert(line);
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
::LogWarning(LOG_HOST, "[AFFVIEW] %s:%u, failed to properly handle peer query request, %s", fneRESTAddress.c_str(), fneRESTPort, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
setFocusWidget(&m_listView);
|
||||
redraw();
|
||||
}
|
||||
|
||||
private:
|
||||
int m_timerId;
|
||||
|
||||
FListView m_listView{this};
|
||||
|
||||
FButton m_refresh{"&Refresh", this};
|
||||
|
||||
/**
|
||||
* @brief Initializes the window layout.
|
||||
*/
|
||||
void initLayout() override
|
||||
{
|
||||
FDialog::setMinimumSize(FSize{PEER_LIST_WIDTH, PEER_LIST_HEIGHT});
|
||||
|
||||
FDialog::setResizeable(false);
|
||||
FDialog::setMinimizable(false);
|
||||
FDialog::setTitlebarButtonVisibility(false);
|
||||
FDialog::setModal(false);
|
||||
|
||||
FDialog::setText("Peers View (25s)");
|
||||
|
||||
initControls();
|
||||
loadListView();
|
||||
|
||||
FDialog::initLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes window controls.
|
||||
*/
|
||||
void initControls()
|
||||
{
|
||||
m_refresh.setGeometry(FPoint(int(getWidth()) - 12, 1), FSize(9, 1));
|
||||
m_refresh.addCallback("clicked", [&]() { loadListView(); });
|
||||
|
||||
m_listView.setGeometry(FPoint{1, 3}, FSize{getWidth() - 1, getHeight() - 5});
|
||||
|
||||
// configure list view columns
|
||||
m_listView.addColumn("Peer ID", 10);
|
||||
m_listView.addColumn("IP Address", 15);
|
||||
m_listView.addColumn("Port", 8);
|
||||
m_listView.addColumn("CC Peer ID", 10);
|
||||
m_listView.addColumn("VC Count", 8);
|
||||
m_listView.addColumn("Connected", 5);
|
||||
m_listView.addColumn("State", 15);
|
||||
m_listView.addColumn("Pings Received", 8);
|
||||
|
||||
// set right alignment for TGID
|
||||
m_listView.setColumnAlignment(1, finalcut::Align::Right);
|
||||
m_listView.setColumnAlignment(4, finalcut::Align::Right);
|
||||
m_listView.setColumnAlignment(6, finalcut::Align::Center);
|
||||
|
||||
// set type of sorting
|
||||
m_listView.setColumnSortType(1, finalcut::SortType::Name);
|
||||
|
||||
// sort by TGID
|
||||
m_listView.setColumnSort(1, finalcut::SortOrder::Ascending);
|
||||
|
||||
setFocusWidget(&m_listView);
|
||||
redraw();
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Event that occurs on keyboard key press.
|
||||
* @param e Keyboard Event.
|
||||
*/
|
||||
void onKeyPress(finalcut::FKeyEvent* e) override
|
||||
{
|
||||
const auto key = e->key();
|
||||
if (key == FKey::F5) {
|
||||
loadListView();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event that occurs on interval by timer.
|
||||
* @param timer Timer Event
|
||||
*/
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
// update timer
|
||||
if (timer->getTimerId() == m_timerId) {
|
||||
loadListView();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __PEER_LIST_WND_H__
|
||||
@ -0,0 +1,739 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file SysViewApplication.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__SYS_VIEW_APPLICATION_H__)
|
||||
#define __SYS_VIEW_APPLICATION_H__
|
||||
|
||||
#include "common/Clock.h"
|
||||
#include "common/dmr/DMRDefines.h"
|
||||
#include "common/dmr/lc/csbk/CSBKFactory.h"
|
||||
#include "common/dmr/lc/LC.h"
|
||||
#include "common/dmr/lc/FullLC.h"
|
||||
#include "common/dmr/SlotType.h"
|
||||
#include "common/dmr/Sync.h"
|
||||
#include "common/p25/P25Defines.h"
|
||||
#include "common/p25/lc/tdulc/TDULCFactory.h"
|
||||
#include "common/p25/lc/tsbk/TSBKFactory.h"
|
||||
#include "common/nxdn/NXDNDefines.h"
|
||||
#include "common/nxdn/lc/RTCH.h"
|
||||
#include "common/Log.h"
|
||||
#include "common/StopWatch.h"
|
||||
#include "network/PeerNetwork.h"
|
||||
#include "SysViewMain.h"
|
||||
#include "SysViewMainWnd.h"
|
||||
|
||||
using namespace system_clock;
|
||||
using namespace network;
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements a color theme for a finalcut application.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API dvmColorTheme final : public FWidgetColors
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the dvmColorTheme class.
|
||||
*/
|
||||
dvmColorTheme()
|
||||
{
|
||||
dvmColorTheme::setColorTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finalizes a instance of the dvmColorTheme class.
|
||||
*/
|
||||
~dvmColorTheme() noexcept override = default;
|
||||
|
||||
/**
|
||||
* @brief Get the Class Name object
|
||||
* @return FString
|
||||
*/
|
||||
auto getClassName() const -> FString override { return "dvmColorTheme"; }
|
||||
/**
|
||||
* @brief Set the Color Theme object
|
||||
*/
|
||||
void setColorTheme() override
|
||||
{
|
||||
term_fg = FColor::Cyan;
|
||||
term_bg = FColor::Blue;
|
||||
|
||||
list_fg = FColor::Black;
|
||||
list_bg = FColor::LightGray;
|
||||
selected_list_fg = FColor::Red;
|
||||
selected_list_bg = FColor::LightGray;
|
||||
|
||||
dialog_fg = FColor::Black;
|
||||
dialog_resize_fg = FColor::LightBlue;
|
||||
dialog_emphasis_fg = FColor::Blue;
|
||||
dialog_bg = FColor::LightGray;
|
||||
|
||||
error_box_fg = FColor::LightRed;
|
||||
error_box_emphasis_fg = FColor::Yellow;
|
||||
error_box_bg = FColor::Black;
|
||||
|
||||
tooltip_fg = FColor::White;
|
||||
tooltip_bg = FColor::Black;
|
||||
|
||||
shadow_fg = FColor::Black;
|
||||
shadow_bg = FColor::LightGray; // only for transparent shadow
|
||||
|
||||
current_element_focus_fg = FColor::White;
|
||||
current_element_focus_bg = FColor::Cyan;
|
||||
current_element_fg = FColor::LightBlue;
|
||||
current_element_bg = FColor::Cyan;
|
||||
current_inc_search_element_fg = FColor::LightRed;
|
||||
selected_current_element_focus_fg = FColor::LightRed;
|
||||
selected_current_element_focus_bg = FColor::Cyan;
|
||||
selected_current_element_fg = FColor::Red;
|
||||
selected_current_element_bg = FColor::Cyan;
|
||||
|
||||
label_fg = FColor::Black;
|
||||
label_bg = FColor::LightGray;
|
||||
label_inactive_fg = FColor::DarkGray;
|
||||
label_inactive_bg = FColor::LightGray;
|
||||
label_hotkey_fg = FColor::Red;
|
||||
label_hotkey_bg = FColor::LightGray;
|
||||
label_emphasis_fg = FColor::Blue;
|
||||
label_ellipsis_fg = FColor::DarkGray;
|
||||
|
||||
inputfield_active_focus_fg = FColor::Yellow;
|
||||
inputfield_active_focus_bg = FColor::Blue;
|
||||
inputfield_active_fg = FColor::LightGray;
|
||||
inputfield_active_bg = FColor::Blue;
|
||||
inputfield_inactive_fg = FColor::Black;
|
||||
inputfield_inactive_bg = FColor::DarkGray;
|
||||
|
||||
toggle_button_active_focus_fg = FColor::Yellow;
|
||||
toggle_button_active_focus_bg = FColor::Blue;
|
||||
toggle_button_active_fg = FColor::LightGray;
|
||||
toggle_button_active_bg = FColor::Blue;
|
||||
toggle_button_inactive_fg = FColor::Black;
|
||||
toggle_button_inactive_bg = FColor::DarkGray;
|
||||
|
||||
button_active_focus_fg = FColor::Yellow;
|
||||
button_active_focus_bg = FColor::Blue;
|
||||
button_active_fg = FColor::White;
|
||||
button_active_bg = FColor::Blue;
|
||||
button_inactive_fg = FColor::Black;
|
||||
button_inactive_bg = FColor::DarkGray;
|
||||
button_hotkey_fg = FColor::Yellow;
|
||||
|
||||
titlebar_active_fg = FColor::Blue;
|
||||
titlebar_active_bg = FColor::White;
|
||||
titlebar_inactive_fg = FColor::Blue;
|
||||
titlebar_inactive_bg = FColor::LightGray;
|
||||
titlebar_button_fg = FColor::Yellow;
|
||||
titlebar_button_bg = FColor::LightBlue;
|
||||
titlebar_button_focus_fg = FColor::LightGray;
|
||||
titlebar_button_focus_bg = FColor::Black;
|
||||
|
||||
menu_active_focus_fg = FColor::Black;
|
||||
menu_active_focus_bg = FColor::White;
|
||||
menu_active_fg = FColor::Black;
|
||||
menu_active_bg = FColor::LightGray;
|
||||
menu_inactive_fg = FColor::DarkGray;
|
||||
menu_inactive_bg = FColor::LightGray;
|
||||
menu_hotkey_fg = FColor::Blue;
|
||||
menu_hotkey_bg = FColor::LightGray;
|
||||
|
||||
statusbar_fg = FColor::Black;
|
||||
statusbar_bg = FColor::LightGray;
|
||||
statusbar_hotkey_fg = FColor::Blue;
|
||||
statusbar_hotkey_bg = FColor::LightGray;
|
||||
statusbar_separator_fg = FColor::Black;
|
||||
statusbar_active_fg = FColor::Black;
|
||||
statusbar_active_bg = FColor::White;
|
||||
statusbar_active_hotkey_fg = FColor::Blue;
|
||||
statusbar_active_hotkey_bg = FColor::White;
|
||||
|
||||
scrollbar_fg = FColor::Cyan;
|
||||
scrollbar_bg = FColor::DarkGray;
|
||||
scrollbar_button_fg = FColor::Yellow;
|
||||
scrollbar_button_bg = FColor::DarkGray;
|
||||
scrollbar_button_inactive_fg = FColor::LightGray;
|
||||
scrollbar_button_inactive_bg = FColor::Black;
|
||||
|
||||
progressbar_fg = FColor::Yellow;
|
||||
progressbar_bg = FColor::Blue;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements the finalcut application.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API SysViewApplication final : public finalcut::FApplication {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the SysViewApplication class.
|
||||
* @param argc Passed argc.
|
||||
* @param argv Passed argv.
|
||||
*/
|
||||
explicit SysViewApplication(const int& argc, char** argv) : FApplication{argc, argv}
|
||||
{
|
||||
m_stopWatch.start();
|
||||
}
|
||||
/**
|
||||
* @brief Finalizes an instance of the SysViewApplication class.
|
||||
*/
|
||||
~SysViewApplication() noexcept override
|
||||
{
|
||||
closePeerNetwork();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes peer network connectivity.
|
||||
* @returns bool
|
||||
*/
|
||||
bool createPeerNetwork()
|
||||
{
|
||||
yaml::Node fne = g_conf["fne"];
|
||||
|
||||
std::string password = fne["password"].as<std::string>();
|
||||
|
||||
std::string address = fne["masterAddress"].as<std::string>();
|
||||
uint16_t port = (uint16_t)fne["masterPort"].as<uint32_t>();
|
||||
uint32_t id = fne["peerId"].as<uint32_t>();
|
||||
|
||||
bool encrypted = fne["encrypted"].as<bool>(false);
|
||||
std::string key = fne["presharedKey"].as<std::string>();
|
||||
uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN];
|
||||
if (!key.empty()) {
|
||||
if (key.size() == 32) {
|
||||
// bryanb: shhhhhhh....dirty nasty hacks
|
||||
key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs)
|
||||
LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself.");
|
||||
}
|
||||
|
||||
if (key.size() == 64) {
|
||||
if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) {
|
||||
const char* keyPtr = key.c_str();
|
||||
::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN);
|
||||
|
||||
for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) {
|
||||
char t[4] = {keyPtr[0], keyPtr[1], 0};
|
||||
presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16);
|
||||
keyPtr += 2 * sizeof(char);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled.");
|
||||
encrypted = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled.");
|
||||
encrypted = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string identity = fne["identity"].as<std::string>();
|
||||
|
||||
LogInfo("Network Parameters");
|
||||
LogInfo(" Peer ID: %u", id);
|
||||
LogInfo(" Address: %s", address.c_str());
|
||||
LogInfo(" Port: %u", port);
|
||||
|
||||
LogInfo(" Encrypted: %s", encrypted ? "yes" : "no");
|
||||
|
||||
if (id > 999999999U) {
|
||||
::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// initialize networking
|
||||
network = new PeerNetwork(address, port, 0U, id, password, true, g_debug, true, true, true, true, true, true, true, true, false);
|
||||
network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, "");
|
||||
network->setLookups(g_ridLookup, g_tidLookup);
|
||||
|
||||
::LogSetNetwork(network);
|
||||
|
||||
if (encrypted) {
|
||||
network->setPresharedKey(presharedKey);
|
||||
}
|
||||
|
||||
network->enable(true);
|
||||
bool ret = network->open();
|
||||
if (!ret) {
|
||||
delete network;
|
||||
network = nullptr;
|
||||
LogError(LOG_HOST, "failed to initialize traffic networking for PEER %u", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
::LogSetNetwork(network);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shuts down peer networking.
|
||||
*/
|
||||
void closePeerNetwork()
|
||||
{
|
||||
if (network != nullptr) {
|
||||
network->close();
|
||||
delete network;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Instance of the peer network.
|
||||
*/
|
||||
network::PeerNetwork* network;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Process external user events.
|
||||
*/
|
||||
void processExternalUserEvent() override
|
||||
{
|
||||
uint32_t ms = m_stopWatch.elapsed();
|
||||
|
||||
ms = m_stopWatch.elapsed();
|
||||
m_stopWatch.start();
|
||||
|
||||
// ------------------------------------------------------
|
||||
// -- Network Clocking --
|
||||
// ------------------------------------------------------
|
||||
|
||||
if (network != nullptr) {
|
||||
network->clock(ms);
|
||||
|
||||
hrc::hrc_t pktTime = hrc::now();
|
||||
|
||||
uint32_t length = 0U;
|
||||
bool netReadRet = false;
|
||||
UInt8Array dmrBuffer = network->readDMR(netReadRet, length);
|
||||
if (netReadRet) {
|
||||
using namespace dmr;
|
||||
|
||||
uint8_t seqNo = dmrBuffer[4U];
|
||||
|
||||
uint32_t srcId = __GET_UINT16(dmrBuffer, 5U);
|
||||
uint32_t dstId = __GET_UINT16(dmrBuffer, 8U);
|
||||
|
||||
DMRDEF::FLCO::E flco = (dmrBuffer[15U] & 0x40U) == 0x40U ? DMRDEF::FLCO::PRIVATE : DMRDEF::FLCO::GROUP;
|
||||
|
||||
uint32_t slotNo = (dmrBuffer[15U] & 0x80U) == 0x80U ? 2U : 1U;
|
||||
|
||||
DMRDEF::DataType::E dataType = (DMRDEF::DataType::E)(dmrBuffer[15U] & 0x0FU);
|
||||
|
||||
data::NetData dmrData;
|
||||
dmrData.setSeqNo(seqNo);
|
||||
dmrData.setSlotNo(slotNo);
|
||||
dmrData.setSrcId(srcId);
|
||||
dmrData.setDstId(dstId);
|
||||
dmrData.setFLCO(flco);
|
||||
|
||||
bool dataSync = (dmrBuffer[15U] & 0x20U) == 0x20U;
|
||||
bool voiceSync = (dmrBuffer[15U] & 0x10U) == 0x10U;
|
||||
|
||||
if (dataSync) {
|
||||
dmrData.setData(dmrBuffer.get() + 20U);
|
||||
dmrData.setDataType(dataType);
|
||||
dmrData.setN(0U);
|
||||
}
|
||||
else if (voiceSync) {
|
||||
dmrData.setData(dmrBuffer.get() + 20U);
|
||||
dmrData.setDataType(DMRDEF::DataType::VOICE_SYNC);
|
||||
dmrData.setN(0U);
|
||||
}
|
||||
else {
|
||||
uint8_t n = dmrBuffer[15U] & 0x0FU;
|
||||
dmrData.setData(dmrBuffer.get() + 20U);
|
||||
dmrData.setDataType(DMRDEF::DataType::VOICE);
|
||||
dmrData.setN(n);
|
||||
}
|
||||
|
||||
// is this the end of the call stream?
|
||||
if (dataSync && (dataType == DMRDEF::DataType::TERMINATOR_WITH_LC)) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "DMR, invalid TERMINATOR, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
RxStatus status;
|
||||
auto it = std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); });
|
||||
if (it == m_dmrStatus.end()) {
|
||||
LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, srcId = %u, dstId = %u",
|
||||
srcId, dstId);
|
||||
}
|
||||
else {
|
||||
status = it->second;
|
||||
}
|
||||
|
||||
uint64_t duration = hrc::diff(pktTime, status.callStartTime);
|
||||
|
||||
if (std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != m_dmrStatus.end()) {
|
||||
m_dmrStatus.erase(dstId);
|
||||
|
||||
LogMessage(LOG_NET, "DMR, Call End, srcId = %u, dstId = %u, duration = %u",
|
||||
srcId, dstId, duration / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// is this a new call stream?
|
||||
if (dataSync && (dataType == DMRDEF::DataType::VOICE_LC_HEADER)) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "DMR, invalid call, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
auto it = std::find_if(m_dmrStatus.begin(), m_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); });
|
||||
if (it == m_dmrStatus.end()) {
|
||||
// this is a new call stream
|
||||
RxStatus status = RxStatus();
|
||||
status.callStartTime = pktTime;
|
||||
status.srcId = srcId;
|
||||
status.dstId = dstId;
|
||||
status.slotNo = slotNo;
|
||||
m_dmrStatus[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow...
|
||||
|
||||
LogMessage(LOG_NET, "DMR, Call Start, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
}
|
||||
|
||||
// are we receiving a CSBK?
|
||||
if (dmrData.getDataType() == DMRDEF::DataType::CSBK) {
|
||||
uint8_t data[DMRDEF::DMR_FRAME_LENGTH_BYTES + 2U];
|
||||
dmrData.getData(data + 2U);
|
||||
|
||||
std::unique_ptr<lc::CSBK> csbk = lc::csbk::CSBKFactory::createCSBK(data + 2U, DMRDEF::DataType::CSBK);
|
||||
if (csbk != nullptr) {
|
||||
switch (csbk->getCSBKO()) {
|
||||
case DMRDEF::CSBKO::BROADCAST:
|
||||
{
|
||||
lc::csbk::CSBK_BROADCAST* osp = static_cast<lc::csbk::CSBK_BROADCAST*>(csbk.get());
|
||||
if (osp->getAnncType() == DMRDEF::BroadcastAnncType::ANN_WD_TSCC) {
|
||||
LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", dmrData.getSlotNo(), csbk->toString().c_str(),
|
||||
osp->getSystemId(), osp->getLogicalCh1());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", dmrData.getSlotNo(), csbk->toString().c_str(), srcId, dstId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_debug)
|
||||
LogMessage(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length);
|
||||
}
|
||||
|
||||
UInt8Array p25Buffer = network->readP25(netReadRet, length);
|
||||
if (netReadRet) {
|
||||
using namespace p25;
|
||||
|
||||
uint8_t duid = p25Buffer[22U];
|
||||
uint8_t MFId = p25Buffer[15U];
|
||||
|
||||
// process raw P25 data bytes
|
||||
UInt8Array data;
|
||||
uint8_t frameLength = p25Buffer[23U];
|
||||
if (duid == P25DEF::DUID::PDU) {
|
||||
frameLength = length;
|
||||
data = std::unique_ptr<uint8_t[]>(new uint8_t[length]);
|
||||
::memset(data.get(), 0x00U, length);
|
||||
::memcpy(data.get(), p25Buffer.get(), length);
|
||||
}
|
||||
else {
|
||||
if (frameLength <= 24) {
|
||||
data = std::unique_ptr<uint8_t[]>(new uint8_t[frameLength]);
|
||||
::memset(data.get(), 0x00U, frameLength);
|
||||
}
|
||||
else {
|
||||
data = std::unique_ptr<uint8_t[]>(new uint8_t[frameLength]);
|
||||
::memset(data.get(), 0x00U, frameLength);
|
||||
::memcpy(data.get(), p25Buffer.get() + 24U, frameLength);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t lco = p25Buffer[4U];
|
||||
|
||||
uint32_t srcId = __GET_UINT16(p25Buffer, 5U);
|
||||
uint32_t dstId = __GET_UINT16(p25Buffer, 8U);
|
||||
|
||||
uint32_t sysId = (p25Buffer[11U] << 8) | (p25Buffer[12U] << 0);
|
||||
uint32_t netId = __GET_UINT16(p25Buffer, 16U);
|
||||
|
||||
// log call status
|
||||
if (duid != P25DEF::DUID::TSDU && duid != P25DEF::DUID::PDU) {
|
||||
// is this the end of the call stream?
|
||||
if ((duid == P25DEF::DUID::TDU) || (duid == P25DEF::DUID::TDULC)) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "P25, invalid TDU, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
RxStatus status = m_p25Status[dstId];
|
||||
uint64_t duration = hrc::diff(pktTime, status.callStartTime);
|
||||
|
||||
if (std::find_if(m_p25Status.begin(), m_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_p25Status.end()) {
|
||||
m_p25Status.erase(dstId);
|
||||
|
||||
LogMessage(LOG_NET, "P25, Call End, srcId = %u, dstId = %u, duration = %u",
|
||||
srcId, dstId, duration / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// is this a new call stream?
|
||||
if ((duid != P25DEF::DUID::TDU) && (duid != P25DEF::DUID::TDULC)) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "P25, invalid call, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
auto it = std::find_if(m_p25Status.begin(), m_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; });
|
||||
if (it == m_p25Status.end()) {
|
||||
// this is a new call stream
|
||||
RxStatus status = RxStatus();
|
||||
status.callStartTime = pktTime;
|
||||
status.srcId = srcId;
|
||||
status.dstId = dstId;
|
||||
m_p25Status[dstId] = status;
|
||||
|
||||
LogMessage(LOG_NET, "P25, Call Start, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (duid) {
|
||||
case P25DEF::DUID::TDU:
|
||||
case P25DEF::DUID::TDULC:
|
||||
if (duid == P25DEF::DUID::TDU) {
|
||||
LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
else {
|
||||
std::unique_ptr<lc::TDULC> tdulc = lc::tdulc::TDULCFactory::createTDULC(data.get());
|
||||
if (tdulc == nullptr) {
|
||||
LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC");
|
||||
}
|
||||
else {
|
||||
LogMessage(LOG_NET, P25_TDULC_STR ", srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case P25DEF::DUID::TSDU:
|
||||
std::unique_ptr<lc::TSBK> tsbk = lc::tsbk::TSBKFactory::createTSBK(data.get());
|
||||
if (tsbk == nullptr) {
|
||||
LogWarning(LOG_NET, P25_TSDU_STR ", undecodable TSBK");
|
||||
}
|
||||
else {
|
||||
switch (tsbk->getLCO()) {
|
||||
case P25DEF::TSBKO::IOSP_GRP_VCH:
|
||||
case P25DEF::TSBKO::IOSP_UU_VCH:
|
||||
{
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u",
|
||||
tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchNo(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_UU_ANS:
|
||||
{
|
||||
lc::tsbk::IOSP_UU_ANS* iosp = static_cast<lc::tsbk::IOSP_UU_ANS*>(tsbk.get());
|
||||
if (iosp->getResponse() > 0U) {
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u",
|
||||
tsbk->toString(true).c_str(), iosp->getResponse(), srcId, dstId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_STS_UPDT:
|
||||
{
|
||||
lc::tsbk::IOSP_STS_UPDT* iosp = static_cast<lc::tsbk::IOSP_STS_UPDT*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u",
|
||||
tsbk->toString(true).c_str(), iosp->getStatus(), srcId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_MSG_UPDT:
|
||||
{
|
||||
lc::tsbk::IOSP_MSG_UPDT* iosp = static_cast<lc::tsbk::IOSP_MSG_UPDT*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u",
|
||||
tsbk->toString(true).c_str(), iosp->getMessage(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_RAD_MON:
|
||||
{
|
||||
lc::tsbk::IOSP_RAD_MON* iosp = static_cast<lc::tsbk::IOSP_RAD_MON*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_CALL_ALRT:
|
||||
{
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_ACK_RSP:
|
||||
{
|
||||
lc::tsbk::IOSP_ACK_RSP* iosp = static_cast<lc::tsbk::IOSP_ACK_RSP*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u",
|
||||
tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), dstId, srcId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_EXT_FNCT:
|
||||
{
|
||||
lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast<lc::tsbk::IOSP_EXT_FNCT*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u",
|
||||
tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::ISP_EMERG_ALRM_REQ:
|
||||
{
|
||||
// non-emergency mode is a TSBKO::OSP_DENY_RSP
|
||||
if (!tsbk->getEmergency()) {
|
||||
lc::tsbk::OSP_DENY_RSP* osp = static_cast<lc::tsbk::OSP_DENY_RSP*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u",
|
||||
osp->toString().c_str(), osp->getAIV(), osp->getResponse(), osp->getSrcId(), osp->getDstId());
|
||||
} else {
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", tsbk->toString().c_str(), srcId, dstId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::IOSP_GRP_AFF:
|
||||
{
|
||||
lc::tsbk::IOSP_GRP_AFF* iosp = static_cast<lc::tsbk::IOSP_GRP_AFF*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, srcId = %u, dstId = %u", tsbk->toString().c_str(),
|
||||
iosp->getSysId(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::OSP_U_DEREG_ACK:
|
||||
{
|
||||
lc::tsbk::OSP_U_DEREG_ACK* iosp = static_cast<lc::tsbk::OSP_U_DEREG_ACK*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u",
|
||||
tsbk->toString(true).c_str(), srcId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::OSP_LOC_REG_RSP:
|
||||
{
|
||||
lc::tsbk::OSP_LOC_REG_RSP* osp = static_cast<lc::tsbk::OSP_LOC_REG_RSP*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", osp->toString().c_str(), srcId, dstId);
|
||||
}
|
||||
break;
|
||||
case P25DEF::TSBKO::OSP_ADJ_STS_BCAST:
|
||||
{
|
||||
lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast<lc::tsbk::OSP_ADJ_STS_BCAST*>(tsbk.get());
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u, svcClass = $%02X", tsbk->toString().c_str(),
|
||||
osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", tsbk->toString().c_str(), srcId, dstId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (g_debug)
|
||||
LogMessage(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length);
|
||||
}
|
||||
|
||||
UInt8Array nxdnBuffer = network->readNXDN(netReadRet, length);
|
||||
if (netReadRet) {
|
||||
using namespace nxdn;
|
||||
|
||||
uint8_t messageType = nxdnBuffer[4U];
|
||||
|
||||
uint32_t srcId = __GET_UINT16(nxdnBuffer, 5U);
|
||||
uint32_t dstId = __GET_UINT16(nxdnBuffer, 8U);
|
||||
|
||||
lc::RTCH lc;
|
||||
|
||||
lc.setMessageType(messageType);
|
||||
lc.setSrcId((uint16_t)srcId & 0xFFFFU);
|
||||
lc.setDstId((uint16_t)dstId & 0xFFFFU);
|
||||
|
||||
bool group = (nxdnBuffer[15U] & 0x40U) == 0x40U ? false : true;
|
||||
lc.setGroup(group);
|
||||
|
||||
// specifically only check the following logic for end of call, voice or data frames
|
||||
if ((messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) ||
|
||||
(messageType == NXDDEF::MessageType::RTCH_VCALL || messageType == NXDDEF::MessageType::RTCH_DCALL_HDR ||
|
||||
messageType == NXDDEF::MessageType::RTCH_DCALL_DATA)) {
|
||||
// is this the end of the call stream?
|
||||
if (messageType == NXDDEF::MessageType::RTCH_TX_REL || messageType == NXDDEF::MessageType::RTCH_TX_REL_EX) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "NXDN, invalid TX_REL, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
RxStatus status = m_nxdnStatus[dstId];
|
||||
uint64_t duration = hrc::diff(pktTime, status.callStartTime);
|
||||
|
||||
if (std::find_if(m_nxdnStatus.begin(), m_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != m_nxdnStatus.end()) {
|
||||
m_nxdnStatus.erase(dstId);
|
||||
|
||||
LogMessage(LOG_NET, "NXDN, Call End, srcId = %u, dstId = %u, duration = %u",
|
||||
srcId, dstId, duration / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// is this a new call stream?
|
||||
if ((messageType != NXDDEF::MessageType::RTCH_TX_REL && messageType != NXDDEF::MessageType::RTCH_TX_REL_EX)) {
|
||||
if (srcId == 0U && dstId == 0U) {
|
||||
LogWarning(LOG_NET, "NXDN, invalid call, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
|
||||
auto it = std::find_if(m_nxdnStatus.begin(), m_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; });
|
||||
if (it == m_nxdnStatus.end()) {
|
||||
// this is a new call stream
|
||||
RxStatus status = RxStatus();
|
||||
status.callStartTime = pktTime;
|
||||
status.srcId = srcId;
|
||||
status.dstId = dstId;
|
||||
m_nxdnStatus[dstId] = status;
|
||||
|
||||
LogMessage(LOG_NET, "NXDN, Call Start, srcId = %u, dstId = %u", srcId, dstId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_debug)
|
||||
LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length);
|
||||
}
|
||||
}
|
||||
|
||||
if (ms < 2U)
|
||||
Thread::sleep(1U);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Represents the receive status of a call.
|
||||
*/
|
||||
class RxStatus {
|
||||
public:
|
||||
system_clock::hrc::hrc_t callStartTime;
|
||||
system_clock::hrc::hrc_t lastPacket;
|
||||
uint32_t srcId;
|
||||
uint32_t dstId;
|
||||
uint8_t slotNo;
|
||||
uint32_t streamId;
|
||||
};
|
||||
typedef std::pair<const uint32_t, RxStatus> StatusMapPair;
|
||||
std::unordered_map<uint32_t, RxStatus> m_dmrStatus;
|
||||
std::unordered_map<uint32_t, RxStatus> m_p25Status;
|
||||
std::unordered_map<uint32_t, RxStatus> m_nxdnStatus;
|
||||
|
||||
StopWatch m_stopWatch;
|
||||
};
|
||||
|
||||
#endif // __SYS_VIEW_APPLICATION_H__
|
||||
@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "common/yaml/Yaml.h"
|
||||
#include "common/Log.h"
|
||||
#include "SysViewMain.h"
|
||||
#include "SysViewApplication.h"
|
||||
#include "SysViewMainWnd.h"
|
||||
|
||||
using namespace lookups;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define IS(s) (::strcmp(argv[i], s) == 0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Variables
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string g_progExe = std::string(__EXE_NAME__);
|
||||
std::string g_iniFile = std::string(DEFAULT_CONF_FILE);
|
||||
yaml::Node g_conf;
|
||||
bool g_debug = false;
|
||||
|
||||
bool g_hideLoggingWnd = false;
|
||||
|
||||
lookups::RadioIdLookup* g_ridLookup = nullptr;
|
||||
lookups::TalkgroupRulesLookup* g_tidLookup = nullptr;
|
||||
lookups::IdenTableLookup* g_idenTable = nullptr;
|
||||
|
||||
SysViewApplication* g_app = nullptr;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Helper to print a fatal error message and exit. */
|
||||
|
||||
void fatal(const char* msg, ...)
|
||||
{
|
||||
char buffer[400U];
|
||||
::memset(buffer, 0x20U, 400U);
|
||||
|
||||
va_list vl;
|
||||
va_start(vl, msg);
|
||||
|
||||
::vsprintf(buffer, msg, vl);
|
||||
|
||||
va_end(vl);
|
||||
|
||||
::fprintf(stderr, "%s: FATAL PANIC; %s\n", g_progExe.c_str(), buffer);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes peer network connectivity.
|
||||
* @returns bool
|
||||
*/
|
||||
bool createPeerNetwork()
|
||||
{
|
||||
if (g_app != nullptr)
|
||||
return g_app->createPeerNetwork();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shuts down peer networking.
|
||||
*/
|
||||
void closePeerNetwork()
|
||||
{
|
||||
if (g_app != nullptr)
|
||||
g_app->closePeerNetwork();
|
||||
}
|
||||
|
||||
/* Helper to pring usage the command line arguments. (And optionally an error.) */
|
||||
|
||||
void usage(const char* message, const char* arg)
|
||||
{
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
|
||||
if (message != nullptr) {
|
||||
::fprintf(stderr, "%s: ", g_progExe.c_str());
|
||||
::fprintf(stderr, message, arg);
|
||||
::fprintf(stderr, "\n\n");
|
||||
}
|
||||
|
||||
::fprintf(stdout,
|
||||
"usage: %s [-dvh]"
|
||||
"[--hide-log]"
|
||||
"[-c <configuration file>]"
|
||||
"\n\n"
|
||||
" -d enable debug\n"
|
||||
" -v show version information\n"
|
||||
" -h show this screen\n"
|
||||
"\n"
|
||||
" --hide-log hide interactive logging window on startup\n"
|
||||
"\n"
|
||||
" -c <file> specifies the system view configuration file to use\n"
|
||||
"\n"
|
||||
" -- stop handling options\n",
|
||||
g_progExe.c_str());
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Helper to validate the command line arguments. */
|
||||
|
||||
int checkArgs(int argc, char* argv[])
|
||||
{
|
||||
int i, p = 0;
|
||||
|
||||
// iterate through arguments
|
||||
for (i = 1; i <= argc; i++)
|
||||
{
|
||||
if (argv[i] == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*argv[i] != '-') {
|
||||
continue;
|
||||
}
|
||||
else if (IS("--")) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
else if (IS("-c")) {
|
||||
if (argc-- <= 0)
|
||||
usage("error: %s", "must specify the monitor configuration file to use");
|
||||
g_iniFile = std::string(argv[++i]);
|
||||
|
||||
if (g_iniFile.empty())
|
||||
usage("error: %s", "monitor configuration file cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("--hide-log")) {
|
||||
++p;
|
||||
g_hideLoggingWnd = true;
|
||||
}
|
||||
else if (IS("-d")) {
|
||||
++p;
|
||||
g_debug = true;
|
||||
}
|
||||
else if (IS("-v")) {
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (IS("-h")) {
|
||||
usage(nullptr, nullptr);
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else {
|
||||
usage("unrecognized option `%s'", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0 || p > argc) {
|
||||
p = 0;
|
||||
}
|
||||
|
||||
return ++p;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Program Entry Point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argv[0] != nullptr && *argv[0] != 0)
|
||||
g_progExe = std::string(argv[0]);
|
||||
|
||||
if (argc > 1) {
|
||||
// check arguments
|
||||
int i = checkArgs(argc, argv);
|
||||
if (i < argc) {
|
||||
argc -= i;
|
||||
argv += i;
|
||||
}
|
||||
else {
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize system logging
|
||||
bool ret = ::LogInitialise("", "", 0U, 1U);
|
||||
if (!ret) {
|
||||
::fprintf(stderr, "unable to open the log file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
|
||||
"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
|
||||
"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \
|
||||
">> FNE System View\r\n");
|
||||
|
||||
try {
|
||||
ret = yaml::Parse(g_conf, g_iniFile.c_str());
|
||||
if (!ret) {
|
||||
::fatal("cannot read the configuration file, %s\n", g_iniFile.c_str());
|
||||
}
|
||||
}
|
||||
catch (yaml::OperationException const& e) {
|
||||
::fatal("cannot read the configuration file - %s (%s)", g_iniFile.c_str(), e.message());
|
||||
}
|
||||
|
||||
// setup the finalcut tui
|
||||
SysViewApplication app{argc, argv};
|
||||
g_app = &app;
|
||||
|
||||
SysViewMainWnd wnd{&app};
|
||||
finalcut::FWidget::setMainWidget(&wnd);
|
||||
|
||||
// try to load bandplan identity table
|
||||
std::string idenLookupFile = g_conf["iden_table"]["file"].as<std::string>();
|
||||
uint32_t idenReloadTime = g_conf["iden_table"]["time"].as<uint32_t>(0U);
|
||||
|
||||
if (idenLookupFile.length() <= 0U) {
|
||||
::LogError(LOG_HOST, "No bandplan identity table? This must be defined!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_logDisplayLevel = 0U;
|
||||
|
||||
// try to load radio IDs table
|
||||
std::string ridLookupFile = g_conf["radio_id"]["file"].as<std::string>();
|
||||
uint32_t ridReloadTime = g_conf["radio_id"]["time"].as<uint32_t>(0U);
|
||||
|
||||
LogInfo("Radio Id Lookups");
|
||||
LogInfo(" File: %s", ridLookupFile.length() > 0U ? ridLookupFile.c_str() : "None");
|
||||
if (ridReloadTime > 0U)
|
||||
LogInfo(" Reload: %u mins", ridReloadTime);
|
||||
|
||||
g_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, false);
|
||||
g_ridLookup->read();
|
||||
|
||||
// try to load talkgroup IDs table
|
||||
std::string tidLookupFile = g_conf["talkgroup_id"]["file"].as<std::string>();
|
||||
uint32_t tidReloadTime = g_conf["talkgroup_id"]["time"].as<uint32_t>(0U);
|
||||
|
||||
LogInfo("Talkgroup Rule Lookups");
|
||||
LogInfo(" File: %s", tidLookupFile.length() > 0U ? tidLookupFile.c_str() : "None");
|
||||
if (tidReloadTime > 0U)
|
||||
LogInfo(" Reload: %u mins", tidReloadTime);
|
||||
|
||||
g_tidLookup = new TalkgroupRulesLookup(tidLookupFile, tidReloadTime, false);
|
||||
g_tidLookup->read();
|
||||
|
||||
LogInfo("Iden Table Lookups");
|
||||
LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None");
|
||||
if (idenReloadTime > 0U)
|
||||
LogInfo(" Reload: %u mins", idenReloadTime);
|
||||
|
||||
g_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime);
|
||||
g_idenTable->read();
|
||||
|
||||
// show and start the application
|
||||
wnd.show();
|
||||
|
||||
finalcut::FApplication::setColorTheme<dvmColorTheme>();
|
||||
app.resetColors();
|
||||
app.redraw();
|
||||
|
||||
int _errno = app.exec();
|
||||
::LogFinalise();
|
||||
return _errno;
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file SysViewMain.h
|
||||
* @ingroup fneSysView
|
||||
* @file SysViewMain.cpp
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__SYS_VIEW_MAIN_H__)
|
||||
#define __SYS_VIEW_MAIN_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "common/lookups/RadioIdLookup.h"
|
||||
#include "common/lookups/TalkgroupRulesLookup.h"
|
||||
#include "common/lookups/IdenTableLookup.h"
|
||||
#include "common/yaml/Yaml.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#undef __PROG_NAME__
|
||||
#define __PROG_NAME__ "Digital Voice Modem (DVM) FNE System View"
|
||||
#undef __EXE_NAME__
|
||||
#define __EXE_NAME__ "sysview"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Externs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** @brief */
|
||||
extern std::string g_progExe;
|
||||
/** @brief */
|
||||
extern std::string g_iniFile;
|
||||
/** @brief */
|
||||
extern yaml::Node g_conf;
|
||||
/** @brief */
|
||||
extern bool g_debug;
|
||||
|
||||
/** @brief */
|
||||
extern bool g_hideLoggingWnd;
|
||||
|
||||
/** @brief */
|
||||
extern lookups::RadioIdLookup* g_ridLookup;
|
||||
/** @brief */
|
||||
extern lookups::TalkgroupRulesLookup* g_tidLookup;
|
||||
/** @brief */
|
||||
extern lookups::IdenTableLookup* g_idenTable;
|
||||
|
||||
/**
|
||||
* @brief Helper to trigger a fatal error message. This will cause the program to terminate
|
||||
* immediately with an error message.
|
||||
*
|
||||
* @param msg String format.
|
||||
*
|
||||
* This is a variable argument function.
|
||||
*/
|
||||
extern HOST_SW_API void fatal(const char* msg, ...);
|
||||
|
||||
/**
|
||||
* @brief Initializes peer network connectivity.
|
||||
* @returns bool
|
||||
*/
|
||||
extern HOST_SW_API bool createPeerNetwork();
|
||||
|
||||
/**
|
||||
* @brief Shuts down peer networking.
|
||||
*/
|
||||
extern HOST_SW_API void closePeerNetwork();
|
||||
|
||||
#endif // __SYS_VIEW_MAIN_H__
|
||||
@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - FNE System View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file SysViewMainWnd.h
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
#if !defined(__AFF_VIEW_WND_H__)
|
||||
#define __AFF_VIEW_WND_H__
|
||||
|
||||
#include "common/Log.h"
|
||||
#include "common/Thread.h"
|
||||
|
||||
using namespace lookups;
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
#undef null
|
||||
|
||||
#include "SysViewMain.h"
|
||||
#include "AffListWnd.h"
|
||||
#include "PeerListWnd.h"
|
||||
|
||||
#include "LogDisplayWnd.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define MINIMUM_SUPPORTED_SIZE_WIDTH 83
|
||||
#define MINIMUM_SUPPORTED_SIZE_HEIGHT 30
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements the root window control.
|
||||
* @ingroup fneSysView
|
||||
*/
|
||||
class HOST_SW_API SysViewMainWnd final : public finalcut::FWidget {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the SysViewMainWnd class.
|
||||
* @param widget
|
||||
*/
|
||||
explicit SysViewMainWnd(FWidget* widget = nullptr) : FWidget{widget}
|
||||
{
|
||||
__InternalOutputStream(m_logWnd);
|
||||
|
||||
// file menu
|
||||
m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X
|
||||
m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this);
|
||||
m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this);
|
||||
|
||||
// help menu
|
||||
m_aboutItem.addCallback("clicked", this, [&]() {
|
||||
const FString line(2, UniChar::BoxDrawingsHorizontal);
|
||||
FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n"
|
||||
L"" + __BANNER__ + L"\n"
|
||||
L"Version " + __VER__ + L"\n\n"
|
||||
L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n"
|
||||
L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others",
|
||||
FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this);
|
||||
info.setCenterText();
|
||||
info.show();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AffViewApplication;
|
||||
|
||||
LogDisplayWnd m_logWnd{this};
|
||||
AffListWnd* m_wnd;
|
||||
PeerListWnd *m_peerWnd;
|
||||
|
||||
FString m_line{13, UniChar::BoxDrawingsHorizontal};
|
||||
|
||||
FMenuBar m_menuBar{this};
|
||||
|
||||
FMenu m_fileMenu{"&File", &m_menuBar};
|
||||
FMenuItem m_quitItem{"&Quit", &m_fileMenu};
|
||||
|
||||
FMenu m_helpMenu{"&Help", &m_menuBar};
|
||||
FMenuItem m_aboutItem{"&About", &m_helpMenu};
|
||||
|
||||
FStatusBar m_statusBar{this};
|
||||
FStatusKey m_keyF3{FKey::F3, "Quit", &m_statusBar};
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Event that occurs when the window is shown.
|
||||
* @param e Show Event
|
||||
*/
|
||||
void onShow(FShowEvent* e) override
|
||||
{
|
||||
const auto& rootWidget = getRootWidget();
|
||||
|
||||
createPeerNetwork();
|
||||
|
||||
int fullWidth = rootWidget->getWidth();
|
||||
if (fullWidth < MINIMUM_SUPPORTED_SIZE_WIDTH) {
|
||||
clearArea();
|
||||
::fatal("screen resolution too small must be wider then %u characters, console width = %u", MINIMUM_SUPPORTED_SIZE_WIDTH, fullWidth);
|
||||
}
|
||||
|
||||
int fullHeight = rootWidget->getHeight();
|
||||
if (fullHeight < MINIMUM_SUPPORTED_SIZE_HEIGHT) {
|
||||
clearArea();
|
||||
::fatal("screen resolution too small must be taller then %u characters, console height = %u", MINIMUM_SUPPORTED_SIZE_HEIGHT, fullHeight);
|
||||
}
|
||||
|
||||
int maxWidth = 77;
|
||||
if (rootWidget) {
|
||||
maxWidth = rootWidget->getClientWidth() - 3;
|
||||
}
|
||||
|
||||
int maxHeight = AFF_LIST_HEIGHT;
|
||||
if (rootWidget) {
|
||||
maxHeight = rootWidget->getClientHeight() - 3;
|
||||
}
|
||||
|
||||
m_wnd = new AffListWnd(this);
|
||||
if (maxHeight - 41 < AFF_LIST_HEIGHT)
|
||||
maxHeight = AFF_LIST_HEIGHT + 41;
|
||||
|
||||
m_wnd->setGeometry(FPoint{2, 2}, FSize{AFF_LIST_WIDTH, (size_t)(maxHeight - 41)});
|
||||
|
||||
m_wnd->setModal(false);
|
||||
m_wnd->show();
|
||||
|
||||
m_peerWnd = new PeerListWnd(this);
|
||||
if (maxHeight - 41 < PEER_LIST_HEIGHT)
|
||||
maxHeight = PEER_LIST_HEIGHT + 41;
|
||||
|
||||
m_peerWnd->setGeometry(FPoint{AFF_LIST_WIDTH + 6, 2}, FSize{(size_t)(maxWidth - AFF_LIST_WIDTH - 6), (size_t)(maxHeight - 41)});
|
||||
|
||||
m_peerWnd->setModal(false);
|
||||
m_peerWnd->show();
|
||||
|
||||
m_wnd->raiseWindow();
|
||||
m_wnd->activateWindow();
|
||||
|
||||
redraw();
|
||||
|
||||
if (g_hideLoggingWnd) {
|
||||
const auto& rootWidget = getRootWidget();
|
||||
m_logWnd.setGeometry(FPoint{(int)(rootWidget->getClientWidth() - 81), (int)(rootWidget->getClientHeight() - 1)}, FSize{80, 20});
|
||||
|
||||
m_logWnd.minimizeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Event that occurs when the window is closed.
|
||||
* @param e Close Event
|
||||
*/
|
||||
void onClose(FCloseEvent* e) override
|
||||
{
|
||||
FApplication::closeConfirmationDialog(this, e);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __AFF_VIEW_WND_H__
|
||||
@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: PROPRIETARY
|
||||
/*
|
||||
* Digital Voice Modem - FNE Affiliations View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "common/network/json/json.h"
|
||||
#include "common/p25/dfsi/DFSIDefines.h"
|
||||
#include "common/p25/dfsi/LC.h"
|
||||
#include "common/Utils.h"
|
||||
#include "network/PeerNetwork.h"
|
||||
|
||||
using namespace network;
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the PeerNetwork class. */
|
||||
|
||||
PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
|
||||
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) :
|
||||
Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup)
|
||||
{
|
||||
assert(!address.empty());
|
||||
assert(port > 0U);
|
||||
assert(!password.empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protected Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Writes configuration to the network. */
|
||||
|
||||
bool PeerNetwork::writeConfig()
|
||||
{
|
||||
if (m_loginStreamId == 0U) {
|
||||
LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* software = __NETVER__;
|
||||
|
||||
json::object config = json::object();
|
||||
|
||||
// identity and frequency
|
||||
config["identity"].set<std::string>(m_identity); // Identity
|
||||
config["rxFrequency"].set<uint32_t>(m_rxFrequency); // Rx Frequency
|
||||
config["txFrequency"].set<uint32_t>(m_txFrequency); // Tx Frequency
|
||||
|
||||
// system info
|
||||
json::object sysInfo = json::object();
|
||||
sysInfo["latitude"].set<float>(m_latitude); // Latitude
|
||||
sysInfo["longitude"].set<float>(m_longitude); // Longitude
|
||||
|
||||
sysInfo["height"].set<int>(m_height); // Height
|
||||
sysInfo["location"].set<std::string>(m_location); // Location
|
||||
config["info"].set<json::object>(sysInfo);
|
||||
|
||||
// channel data
|
||||
json::object channel = json::object();
|
||||
channel["txPower"].set<uint32_t>(m_power); // Tx Power
|
||||
channel["txOffsetMhz"].set<float>(m_txOffsetMhz); // Tx Offset (Mhz)
|
||||
channel["chBandwidthKhz"].set<float>(m_chBandwidthKhz); // Ch. Bandwidth (khz)
|
||||
channel["channelId"].set<uint8_t>(m_channelId); // Channel ID
|
||||
channel["channelNo"].set<uint32_t>(m_channelNo); // Channel No
|
||||
config["channel"].set<json::object>(channel);
|
||||
|
||||
// RCON
|
||||
json::object rcon = json::object();
|
||||
rcon["password"].set<std::string>(m_restApiPassword); // REST API Password
|
||||
rcon["port"].set<uint16_t>(m_restApiPort); // REST API Port
|
||||
config["rcon"].set<json::object>(rcon);
|
||||
|
||||
bool convPeer = true;
|
||||
config["conventionalPeer"].set<bool>(convPeer); // Conventional Peer Marker
|
||||
config["software"].set<std::string>(std::string(software)); // Software ID
|
||||
|
||||
json::value v = json::value(config);
|
||||
std::string json = v.serialize();
|
||||
|
||||
char buffer[json.length() + 8U];
|
||||
|
||||
::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U);
|
||||
::sprintf(buffer + 8U, "%s", json.c_str());
|
||||
|
||||
if (m_debug) {
|
||||
Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U);
|
||||
}
|
||||
|
||||
return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, pktSeq(), m_loginStreamId);
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: PROPRIETARY
|
||||
/*
|
||||
* Digital Voice Modem - FNE Affiliations View
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @defgroup fneAffView_network Networking
|
||||
* @brief Implementation for the FNE networking.
|
||||
* @ingroup fneAffView
|
||||
*
|
||||
* @file PeerNetwork.h
|
||||
* @ingroup fneAffView_network
|
||||
* @file PeerNetwork.cpp
|
||||
* @ingroup fneAffView_network
|
||||
*/
|
||||
#if !defined(__PEER_NETWORK_H__)
|
||||
#define __PEER_NETWORK_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "host/network/Network.h"
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace network
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// Implements the core peer networking logic.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API PeerNetwork : public Network {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the PeerNetwork class.
|
||||
* @param address Network Hostname/IP address to connect to.
|
||||
* @param port Network port number.
|
||||
* @param local
|
||||
* @param peerId Unique ID on the network.
|
||||
* @param password Network authentication password.
|
||||
* @param duplex Flag indicating full-duplex operation.
|
||||
* @param debug Flag indicating whether network debug is enabled.
|
||||
* @param dmr Flag indicating whether DMR is enabled.
|
||||
* @param p25 Flag indicating whether P25 is enabled.
|
||||
* @param nxdn Flag indicating whether NXDN is enabled.
|
||||
* @param slot1 Flag indicating whether DMR slot 1 is enabled for network traffic.
|
||||
* @param slot2 Flag indicating whether DMR slot 2 is enabled for network traffic.
|
||||
* @param allowActivityTransfer Flag indicating that the system activity logs will be sent to the network.
|
||||
* @param allowDiagnosticTransfer Flag indicating that the system diagnostic logs will be sent to the network.
|
||||
* @param updateLookup Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network.
|
||||
*/
|
||||
PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
|
||||
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Writes configuration to the network.
|
||||
* @returns bool True, if configuration was sent, otherwise false.
|
||||
*/
|
||||
bool writeConfig() override;
|
||||
};
|
||||
} // namespace network
|
||||
|
||||
#endif // __PEER_NETWORK_H__
|
||||
Loading…
Reference in new issue