initial implementation for a passive monitor tool; correct bad code style; implement feedback of last destination ID; enhance status REST API;

pull/33/head
Bryan Biedenkapp 3 years ago
parent ff97cc5ada
commit 8f7763cac4

@ -144,6 +144,42 @@ file(GLOB dvmcmd_SRC
"src/Utils.cpp"
)
#
## dvmmon source/header files
#
file(GLOB dvmmon_SRC
"src/lookups/AffiliationLookup.h"
"src/lookups/LookupTable.h"
"src/lookups/IdenTableLookup.h"
"src/lookups/IdenTableLookup.cpp"
"src/modem/Modem.h"
"src/monitor/*.h"
"src/monitor/*.cpp"
"src/network/UDPSocket.h"
"src/network/UDPSocket.cpp"
"src/network/RESTDefines.h"
"src/network/json/*.h"
"src/network/rest/*.h"
"src/network/rest/*.cpp"
"src/network/rest/http/*.h"
"src/network/rest/http/*.cpp"
"src/remote/RESTClient.h"
"src/remote/RESTClient.cpp"
"src/edac/SHA256.h"
"src/edac/SHA256.cpp"
"src/yaml/*.h"
"src/yaml/*.cpp"
"src/Defines.h"
"src/Thread.h"
"src/Thread.cpp"
"src/Timer.h"
"src/Timer.cpp"
"src/Log.h"
"src/Log.cpp"
"src/Utils.h"
"src/Utils.cpp"
)
# Digital mode options and other compilation features
option(ENABLE_DMR "Enable DMR Digtial Mode" on)
if (ENABLE_DMR)
@ -350,3 +386,24 @@ target_link_libraries(asio::asio INTERFACE Threads::Threads)
add_executable(dvmcmd ${dvmcmd_SRC})
target_link_libraries(dvmcmd PRIVATE asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE src)
#
## dvmmon project
#
if (ENABLE_TUI_SUPPORT)
project(dvmmon)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
find_package(Threads REQUIRED)
# add ASIO
target_include_directories(asio::asio INTERFACE ${ASIO_INCLUDE_DIR})
target_compile_definitions(asio::asio INTERFACE "ASIO_STANDALONE")
target_link_libraries(asio::asio INTERFACE Threads::Threads)
# add finalcut
target_include_directories(finalcut INTERFACE ${FINALCUT_INCLUDE_DIR})
add_executable(dvmmon ${dvmmon_SRC})
target_link_libraries(dvmmon PRIVATE asio::asio finalcut Threads::Threads)
target_include_directories(dvmmon PRIVATE src)
endif (ENABLE_TUI_SUPPORT)

@ -630,6 +630,26 @@ void Control::setCSBKVerbose(bool verbose)
lc::CSBK::setVerbose(verbose);
}
/// <summary>
/// Helper to get the last transmitted destination ID.
/// </summary>
/// <param name="slotNo">DMR slot number.</param>
/// <returns></returns>
uint32_t Control::getLastDstId(uint32_t slotNo) const
{
switch (slotNo) {
case 1U:
return m_slot1->getLastDstId();
case 2U:
return m_slot2->getLastDstId();
default:
LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo);
break;
}
return 0U;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -130,6 +130,9 @@ namespace dmr
/// <summary>Helper to change the CSBK verbose state.</summary>
void setCSBKVerbose(bool verbose);
/// <summary>Helper to get the last transmitted destination ID.</summary>
uint32_t getLastDstId(uint32_t slotNo) const;
private:
friend class Slot;

@ -676,6 +676,23 @@ void Slot::setSilenceThreshold(uint32_t threshold)
m_silenceThreshold = threshold;
}
/// <summary>
/// Helper to get the last transmitted destination ID.
/// </summary>
/// <returns></returns>
uint32_t Slot::getLastDstId() const
{
if (m_rfLastDstId != 0U) {
return m_rfLastDstId;
}
if (m_netLastDstId != 0U) {
return m_netLastDstId;
}
return 0U;
}
/// <summary>
/// Helper to initialize the DMR slot processor.
/// </summary>

@ -121,6 +121,9 @@ namespace dmr
/// <summary>Helper to set the voice error silence threshold.</summary>
void setSilenceThreshold(uint32_t threshold);
/// <summary>Helper to get the last transmitted destination ID.</summary>
uint32_t getLastDstId() const;
/// <summary>Helper to initialize the slot processor.</summary>
static void init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem,
network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup,

@ -99,6 +99,7 @@ Host::Host(const std::string& confFile) :
m_rfModeHang(10U),
m_rfTalkgroupHang(10U),
m_netModeHang(3U),
m_lastDstId(0U),
m_identity(),
m_cwCallsign(),
m_cwIdTime(0U),
@ -889,6 +890,8 @@ int Host::run()
m_modeTimer.start();
}
m_lastDstId = dmr->getLastDstId(1U);
}
}
@ -934,6 +937,8 @@ int Host::run()
m_modeTimer.start();
}
m_lastDstId = dmr->getLastDstId(2U);
}
}
}
@ -972,6 +977,8 @@ int Host::run()
m_modeTimer.start();
}
m_lastDstId = p25->getLastDstId();
}
else {
nextLen = 0U;
@ -1050,6 +1057,8 @@ int Host::run()
m_modeTimer.start();
}
m_lastDstId = nxdn->getLastDstId();
}
}
}

@ -99,6 +99,8 @@ private:
uint32_t m_rfTalkgroupHang;
uint32_t m_netModeHang;
uint32_t m_lastDstId;
std::string m_identity;
std::string m_cwCallsign;
uint32_t m_cwIdTime;

@ -88,7 +88,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
yaml::Node rfssConfig = m_setup->m_conf["system"]["config"];
m_setup->m_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);

@ -77,7 +77,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
// symbol levels
{

@ -82,7 +82,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
// symbol levels
{

@ -85,7 +85,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
// gain
{

@ -85,7 +85,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
yaml::Node logConf = m_setup->m_conf["log"];
uint32_t logLevel = logConf["fileLevel"].as<uint32_t>(1U);

@ -105,7 +105,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
yaml::Node cwId = m_setup->m_conf["system"]["cwId"];
bool enabled = cwId["enable"].as<bool>(false);

@ -82,7 +82,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
// symbol levels
{

@ -99,7 +99,7 @@ private:
/// <summary>
///
/// </summary>
void initControls()
void initControls() override
{
yaml::Node modemConfig = m_setup->m_conf["system"]["modem"];
m_setup->m_conf["system"]["modem"]["protocol"]["type"] = std::string("uart"); // configuring modem, always sets type to UART

@ -0,0 +1,137 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__LOG_DISPLAY_WND_H__)
#define __LOG_DISPLAY_WND_H__
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the log display window.
// ---------------------------------------------------------------------------
class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream
{
public:
/// <summary>
/// Initializes a new instance of the LogDisplayWnd class.
/// </summary>
/// <param name="widget"></param>
explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget}
{
m_scrollText.ignorePadding();
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
}
/// <summary>Copy constructor.</summary>
LogDisplayWnd(const LogDisplayWnd&) = delete;
/// <summary>Move constructor.</summary>
LogDisplayWnd(LogDisplayWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the LogDisplayWnd class.</summary>
~LogDisplayWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete;
private:
FTextView m_scrollText{this};
int m_timerId;
/// <summary>
///
/// </summary>
void initLayout() override
{
using namespace std::string_literals;
auto lightning = "\u26a1";
FDialog::setText("System Log"s + lightning);
const auto& rootWidget = getRootWidget();
FDialog::setGeometry(FPoint{(int)(rootWidget->getClientWidth() - 81), (int)(rootWidget->getClientHeight() - 20)}, FSize{80, 20});
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();
}
/// <summary>
///
/// </summary>
void adjustSize() override
{
FDialog::adjustSize();
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
redraw();
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
void onClose(FCloseEvent* e) override
{
minimizeWindow();
}
/// <summary>
///
/// </summary>
/// <param name="timer"></param>
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,83 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__MONITOR_APPLICATION_H__)
#define __MONITOR_APPLICATION_H__
#include "monitor/MonitorMain.h"
#include "monitor/MonitorMainWnd.h"
#include "Log.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the finalcut application.
// ---------------------------------------------------------------------------
class HOST_SW_API MonitorApplication final : public finalcut::FApplication {
public:
/// <summary>
/// Initializes a new instance of the MonitorApplication class.
/// </summary>
/// <param name="argc"></param>
/// <param name="argv"></param>
explicit MonitorApplication(const int& argc, char** argv) : FApplication{argc, argv}
{
m_statusRefreshTimer = addTimer(1000);
}
protected:
/// <summary>
///
/// </summary>
virtual void processExternalUserEvent()
{
/* stub */
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="timer"></param>
void onTimer(FTimerEvent* timer) override
{
if (timer != nullptr) {
if (timer->getTimerId() == m_statusRefreshTimer) {
/* stub */
}
}
}
private:
int m_statusRefreshTimer;
};
#endif // __MONITOR_APPLICATION_H__

@ -0,0 +1,258 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp <gatekeep@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "Defines.h"
#include "Log.h"
#include "monitor/MonitorMain.h"
#include "monitor/MonitorApplication.h"
#include "monitor/MonitorMainWnd.h"
#include "yaml/Yaml.h"
#include "Utils.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;
lookups::IdenTableLookup* g_idenTable = nullptr;
uint32_t g_channelId = 0U;
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to print a fatal error message and exit.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="msg">Message.</param>
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: %s\n", g_progExe.c_str(), buffer);
exit(EXIT_FAILURE);
}
/// <summary>
/// Helper to pring usage the command line arguments. (And optionally an error.)
/// </summary>
/// <param name="message">Error message.</param>
/// <param name="arg">Error message arguments.</param>
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2023 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] [-c <configuration file>]\n\n"
" -c <file> specifies the configuration file to use\n"
"\n"
" -d enable debug\n"
" -v show version information\n"
" -h show this screen\n"
" -- stop handling options\n",
g_progExe.c_str());
exit(EXIT_FAILURE);
}
/// <summary>
/// Helper to validate the command line arguments.
/// </summary>
/// <param name="argc">Argument count.</param>
/// <param name="argv">Array of argument strings.</param>
/// <returns>Count of remaining unprocessed arguments.</returns>
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 configuration file to use");
g_iniFile = std::string(argv[++i]);
if (g_iniFile == "")
usage("error: %s", "configuration file cannot be blank!");
p += 2;
}
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-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\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;
}
getHostVersion();
::LogInfo(">> Monitor");
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
MonitorApplication app{argc, argv};
MonitorMainWnd wnd{&app};
finalcut::FWidget::setMainWidget(&wnd);
yaml::Node systemConf = g_conf["system"];
// try to load bandplan identity table
std::string idenLookupFile = systemConf["iden_table"]["file"].as<std::string>();
uint32_t idenReloadTime = systemConf["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;
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();
yaml::Node rfssConfig = systemConf["config"];
g_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
if (g_channelId > 15U) { // clamp to 15
g_channelId = 15U;
}
// show and start the application
wnd.show();
finalcut::FApplication::setDarkTheme();
app.resetColors();
app.redraw();
int _errno = app.exec();
::LogFinalise();
return _errno;
}

@ -0,0 +1,56 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__MONITOR_MAIN_H__)
#define __MONITOR_MAIN_H__
#include "Defines.h"
#include "lookups/IdenTableLookup.h"
#include "yaml/Yaml.h"
#include <string>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#undef __PROG_NAME__
#define __PROG_NAME__ "Digital Voice Modem (DVM) Monitor Tool"
#undef __EXE_NAME__
#define __EXE_NAME__ "dvmmon"
// ---------------------------------------------------------------------------
// Externs
// ---------------------------------------------------------------------------
extern std::string g_progExe;
extern std::string g_iniFile;
extern yaml::Node g_conf;
extern bool g_debug;
extern lookups::IdenTableLookup* g_idenTable;
extern uint32_t g_channelId;
#endif // __MONITOR_MAIN_H__

@ -0,0 +1,266 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__MONITOR_WND_H__)
#define __MONITOR_WND_H__
#include "lookups/AffiliationLookup.h"
#include "Log.h"
#include "Thread.h"
using namespace lookups;
#include <final/final.h>
using namespace finalcut;
#undef null
#include "monitor/MonitorMain.h"
#include "monitor/LogDisplayWnd.h"
#include "monitor/NodeStatusWnd.h"
#include <vector>
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
class HOST_SW_API MonitorApplication;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the root window control.
// ---------------------------------------------------------------------------
class HOST_SW_API MonitorMainWnd final : public finalcut::FWidget {
public:
/// <summary>
/// Initializes a new instance of the MonitorMainWnd class.
/// </summary>
/// <param name="widget"></param>
explicit MonitorMainWnd(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);
// command menu
m_cmdMenuSeparator1.setSeparator();
m_cmdMenuSeparator2.setSeparator();
// engineering menu
m_engineeringMenuSeparator1.setSeparator();
m_engineeringMenuSeparator2.setSeparator();
// help menu
m_aboutItem.addCallback("clicked", this, [&]() {
const FString line(2, UniChar::BoxDrawingsHorizontal);
FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n"
L"Version " + __VER__ + L"\n\n"
L"Copyright (c) 2017-2023 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 MonitorApplication;
LogDisplayWnd m_logWnd{this};
std::vector<NodeStatusWnd*> m_nodes;
std::vector<uint32_t> m_voiceChNo;
std::unordered_map<uint32_t, lookups::VoiceChData> m_voiceChData;
FString m_line{13, UniChar::BoxDrawingsHorizontal};
FMenuBar m_menuBar{this};
FMenu m_fileMenu{"&File", &m_menuBar};
FMenuItem m_quitItem{"&Quit", &m_fileMenu};
FMenu m_cmdMenu{"&Commands", &m_menuBar};
FMenuItem m_pageSU{"&Page Subscriber", &m_cmdMenu};
FMenuItem m_radioCheckSU{"Radio &Check Subscriber", &m_cmdMenu};
FMenuItem m_cmdMenuSeparator1{&m_cmdMenu};
FMenuItem m_inhibitSU{"&Inhibit Subscriber", &m_cmdMenu};
FMenuItem m_uninhibitSU{"&Uninhibit Subscriber", &m_cmdMenu};
FMenuItem m_cmdMenuSeparator2{&m_cmdMenu};
FMenuItem m_gaqSU{"&Group Affiliation Query", &m_cmdMenu};
FMenuItem m_uregSU{"&Force Unit Registration", &m_cmdMenu};
FMenu m_engineeringMenu{"&Engineering", &m_menuBar};
FMenuItem m_toggleDMRDebug{"Toggle DMR Debug", &m_engineeringMenu};
FMenuItem m_toggleDMRCSBKDump{"Toggle DMR CSBK Dump", &m_engineeringMenu};
FMenuItem m_engineeringMenuSeparator1{&m_engineeringMenu};
FMenuItem m_toggleP25Debug{"Toggle P25 Debug", &m_engineeringMenu};
FMenuItem m_toggleP25TSBKDump{"Toggle P25 TSBK Dump", &m_engineeringMenu};
FMenuItem m_engineeringMenuSeparator2{&m_engineeringMenu};
FMenuItem m_toggleNXDNDebug{"Toggle NXDN Debug", &m_engineeringMenu};
FMenuItem m_toggleNXDNRCCHDump{"Toggle NXDN RCCH Dump", &m_engineeringMenu};
FMenu m_helpMenu{"&Help", &m_menuBar};
FMenuItem m_aboutItem{"&About", &m_helpMenu};
FStatusBar m_statusBar{this};
FStatusKey m_keyF3{FKey::F3, "Quit", &m_statusBar};
/// <summary>
///
/// </summary>
void intializeNodeDisplay()
{
const auto& rootWidget = getRootWidget();
const int defaultOffsX = 2;
int offsX = defaultOffsX, offsY = 2;
int maxWidth = 77;
if (rootWidget) {
maxWidth = rootWidget->getClientWidth() - 3;
}
yaml::Node systemConf = g_conf["system"];
yaml::Node rfssConfig = systemConf["config"];
uint8_t channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
if (channelId > 15U) { // clamp to 15
channelId = 15U;
}
// main/control channel
uint32_t mainChNo = 0U;
{
yaml::Node networkConf = g_conf["network"];
uint32_t chNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as<std::string>("1").c_str(), NULL, 16);
if (chNo == 0U) { // clamp to 1
chNo = 1U;
}
if (chNo > 4095U) { // clamp to 4095
chNo = 4095U;
}
mainChNo = chNo;
//std::string restApiAddress = networkConf["restAddress"].as<std::string>("127.0.0.1");
std::string restApiAddress = std::string("127.0.0.1");
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
// create configuration file node
NodeStatusWnd* wnd = new NodeStatusWnd(this);
wnd->setChData(data);
wnd->setChannelId(channelId);
wnd->setChannelNo(chNo);
wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
offsX += NODE_STATUS_WIDTH + 2;
m_nodes.push_back(wnd);
}
/*
** Voice Channels
*/
yaml::Node& voiceChList = rfssConfig["voiceChNo"];
if (voiceChList.size() != 0U) {
for (size_t i = 0; i < voiceChList.size(); i++) {
yaml::Node& channel = voiceChList[i];
uint32_t chNo = (uint32_t)::strtoul(channel["channelNo"].as<std::string>("1").c_str(), NULL, 16);
if (chNo == 0U) { // clamp to 1
chNo = 1U;
}
if (chNo > 4095U) { // clamp to 4095
chNo = 4095U;
}
if (chNo == mainChNo) {
continue;
}
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Adddress %s:%u", g_channelId, chNo, restApiAddress.c_str(), restApiPort);
VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
NodeStatusWnd* wnd = new NodeStatusWnd(this);
wnd->setChData(data);
wnd->setChannelId(channelId);
wnd->setChannelNo(chNo);
// set control position
if (offsX + NODE_STATUS_WIDTH > maxWidth) {
offsY += NODE_STATUS_HEIGHT + 2;
offsX = defaultOffsX;
}
wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
offsX += NODE_STATUS_WIDTH + 2;
m_nodes.push_back(wnd);
}
}
// display all the node windows
for (auto* wnd : m_nodes) {
wnd->setModal(false);
wnd->show();
}
redraw();
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
void onShow(FShowEvent* e) override
{
intializeNodeDisplay();
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
void onClose(FCloseEvent* e) override
{
FApplication::closeConfirmationDialog(this, e);
}
};
#endif // __MONITOR_WND_H__

@ -0,0 +1,342 @@
/**
* Digital Voice Modem - Monitor
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Monitor
*
*/
/*
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(__NODE_STATUS_WND_H__)
#define __NODE_STATUS_WND_H__
#include "lookups/AffiliationLookup.h"
#include "modem/Modem.h"
#include "network/RESTDefines.h"
#include "remote/RESTClient.h"
#include "monitor/MonitorMainWnd.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define NODE_STATUS_WIDTH 28
#define NODE_STATUS_HEIGHT 7
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the node status display window.
// ---------------------------------------------------------------------------
class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog
{
public:
/// <summary>
/// Initializes a new instance of the NodeStatusWnd class.
/// </summary>
/// <param name="widget"></param>
explicit NodeStatusWnd(FWidget* widget = nullptr) : FDialog{widget}
{
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
m_reconnectTimerId = addTimer(15000); // starts the timer every 10 seconds
}
/// <summary>Copy constructor.</summary>
NodeStatusWnd(const NodeStatusWnd&) = delete;
/// <summary>Move constructor.</summary>
NodeStatusWnd(NodeStatusWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the NodeStatusWnd class.</summary>
~NodeStatusWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const NodeStatusWnd&) -> NodeStatusWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (NodeStatusWnd&&) noexcept -> NodeStatusWnd& = delete;
/// <summary>Disable set X coordinate.</summary>
void setX(int, bool = true) override { }
/// <summary>Disable set Y coordinate.</summary>
void setY(int, bool = true) override { }
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
/// <summary>Sets the channel data.</summary>
void setChData(lookups::VoiceChData chData) { m_chData = chData; }
/// <summary>Sets the channel number.</summary>
void setChannelId(uint8_t channelId) { m_channelId = channelId; }
/// <summary>Sets the channel number.</summary>
void setChannelNo(uint32_t channelNo) { m_channelNo = channelNo; }
private:
int m_timerId;
int m_reconnectTimerId;
bool m_failed;
bool m_control;
bool m_tx;
lookups::VoiceChData m_chData;
uint8_t m_channelId;
uint32_t m_channelNo;
FLabel m_channelNoLabel{"Ch. No.: ", this};
FLabel m_chanNo{this};
FLabel m_txFreqLabel{"Tx: ", this};
FLabel m_txFreq{this};
FLabel m_rxFreqLabel{"Rx: ", this};
FLabel m_rxFreq{this};
FLabel m_lastTGLabel{"Last TG: ", this};
FLabel m_lastTG{this};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setMinimumSize(FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
FDialog::setResizeable(false);
FDialog::setMinimizable(false);
FDialog::setTitlebarButtonVisibility(false);
FDialog::setShadow(false);
FDialog::setModal(false);
FDialog::setText("UNKNOWN");
initControls();
FDialog::initLayout();
}
/// <summary>
///
/// </summary>
void draw() override
{
FDialog::draw();
if (m_failed) {
setColor(FColor::Yellow1, FColor::Red3);
}
else if (m_control) {
setColor(FColor::LightGray, FColor::Purple3);
}
else if (m_tx) {
setColor(FColor::LightGray, FColor::Green3);
}
else {
setColor(FColor::LightGray, FColor::Black);
}
finalcut::drawBorder(this, FRect(FPoint{1, 2}, FPoint{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT}));
}
/// <summary>
///
/// </summary>
void initControls()
{
// channel number
{
m_channelNoLabel.setGeometry(FPoint(2, 1), FSize(10, 1));
m_chanNo.setGeometry(FPoint(11, 1), FSize(8, 1));
m_chanNo.setText(__INT_STR(m_channelNo));
}
// channel frequency
{
IdenTable entry = g_idenTable->find(m_channelId);
if (entry.baseFrequency() == 0U) {
::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId);
}
if (entry.txOffsetMhz() == 0U) {
::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId);
}
uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125);
float calcTxOffset = entry.txOffsetMhz() * 1000000;
uint32_t rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset);
uint32_t txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)));
m_txFreqLabel.setGeometry(FPoint(2, 2), FSize(4, 1));
m_txFreq.setGeometry(FPoint(6, 2), FSize(8, 1));
std::stringstream ss;
ss << std::fixed << std::setprecision(4) << (float)(txFrequency / 1000000.0f);
m_txFreq.setText(ss.str());
m_rxFreqLabel.setGeometry(FPoint(2, 3), FSize(4, 1));
m_rxFreq.setGeometry(FPoint(6, 3), FSize(8, 1));
ss.str(std::string());
ss << std::fixed << std::setprecision(4) << (float)(rxFrequency / 1000000.0f);
m_rxFreq.setText(ss.str());
}
// last TG
{
m_lastTGLabel.setGeometry(FPoint(2, 4), FSize(10, 1));
m_lastTG.setGeometry(FPoint(11, 4), FSize(8, 1));
m_lastTG.setText("None");
}
}
/// <summary>
///
/// </summary>
/// <param name="timer"></param>
void onTimer(FTimerEvent* timer) override
{
if (timer != nullptr) {
// update timer
if (timer->getTimerId() == m_timerId) {
if (!m_failed) {
// 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(m_chData.address(), m_chData.port(), m_chData.password(),
HTTP_GET, GET_STATUS, req, rsp, g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
m_failed = true;
setText("FAILED");
}
else {
// get remote node state
if (rsp["dmrTSCCEnable"].is<bool>() && rsp["p25CtrlEnable"].is<bool>() &&
rsp["nxdnCtrlEnable"].is<bool>()) {
bool dmrTSCCEnable = rsp["dmrTSCCEnable"].get<bool>();
bool dmrCC = rsp["dmrCC"].get<bool>();
bool p25CtrlEnable = rsp["p25CtrlEnable"].get<bool>();
bool p25CC = rsp["p25CC"].get<bool>();
bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = rsp["nxdnCC"].get<bool>();
// are we a dedicated control channel?
if (dmrCC || p25CC || nxdnCC) {
m_control = true;
setText("CONTROL");
}
// if we aren't a dedicated control channel; set our
// title bar appropriately and set Tx state
if (!m_control) {
if (dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) {
setText("ENH. VOICE/CONV");
}
else {
setText("VOICE/CONV");
}
// are we transmitting?
if (rsp["tx"].is<bool>()) {
m_tx = rsp["tx"].get<bool>();
}
else {
::LogWarning(LOG_HOST, "%s:%u, does not report Tx status");
m_tx = false;
}
}
}
// get the remote node channel information
if (rsp["channelId"].is<uint8_t>() && rsp["channelNo"].is<uint32_t>()) {
uint8_t channelId = rsp["channelId"].get<uint8_t>();
uint32_t channelNo = rsp["channelNo"].get<uint32_t>();
bool resetControls = false;
// verify channel ID matches our configuration
if (channelId != m_channelId) {
::LogWarning(LOG_HOST, "%s:%u, reports chId = %u, disagrees with chId = %u", m_chData.address().c_str(), m_chData.port(), channelId, m_channelId);
m_channelId = channelId;
resetControls = true;
}
// verify channel number matches our configuration
if (channelNo != m_channelNo) {
::LogWarning(LOG_HOST, "%s:%u, reports chNo = %u, disagrees with chNo = %u", m_chData.address().c_str(), m_chData.port(), channelNo, m_channelNo);
m_channelNo = channelNo;
resetControls = true;
}
// reset controls for our display if necessary
if (resetControls) {
initControls();
setText(getText() + "*");
}
}
else {
::LogWarning(LOG_HOST, "%s:%u, does not report channel information");
}
// report last known transmitted destination ID
if (rsp["lastDstId"].is<uint32_t>()) {
uint32_t lastDstId = rsp["lastDstId"].get<uint32_t>();
if (lastDstId == 0) {
m_lastTG.setText("None");
}
else {
m_lastTG.setText(__INT_STR(lastDstId));
}
}
else {
::LogWarning(LOG_HOST, "%s:%u, does not report last TG information");
}
}
}
redraw();
}
// reconnect timer
if (timer->getTimerId() == m_reconnectTimerId) {
if (m_failed) {
::LogInfoEx(LOG_HOST, "attempting to reconnect to %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
// callback REST API to get status of the channel we represent
json::object req = json::object();
int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(),
HTTP_GET, GET_STATUS, req, g_debug);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
}
else {
m_failed = false;
setText("UNKNOWN");
}
}
redraw();
}
}
}
};
#endif // __NODE_STATUS_WND_H__

@ -508,8 +508,11 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply,
uint8_t protoVer = m_host->m_modem->getVersion();
response["protoVer"].set<uint8_t>(protoVer);
response["dmrTSCCEnable"].set<bool>(m_host->m_dmrTSCCData);
response["dmrCC"].set<bool>(m_host->m_dmrCtrlChannel);
response["p25CtrlEnable"].set<bool>(m_host->m_p25CCData);
response["p25CC"].set<bool>(m_host->m_p25CtrlChannel);
response["nxdnCtrlEnable"].set<bool>(m_host->m_nxdnCCData);
response["nxdnCC"].set<bool>(m_host->m_nxdnCtrlChannel);
yaml::Node p25Protocol = m_host->m_conf["protocols"]["p25"];
@ -519,6 +522,13 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply,
response["p25VOC"].set<bool>(p25VOC);
response["nxdnVOC"].set<bool>(nxdnVOC);
response["tx"].set<bool>(m_host->m_modem->m_tx);
response["channelId"].set<uint8_t>(m_host->m_channelId);
response["channelNo"].set<uint32_t>(m_host->m_channelNo);
response["lastDstId"].set<uint32_t>(m_host->m_lastDstId);
}
{

@ -748,6 +748,23 @@ void Control::setRCCHVerbose(bool verbose)
lc::RTCH::setVerbose(verbose);
}
/// <summary>
/// Helper to get the last transmitted destination ID.
/// </summary>
/// <returns></returns>
uint32_t Control::getLastDstId() const
{
if (m_rfLastDstId != 0U) {
return m_rfLastDstId;
}
if (m_netLastDstId != 0U) {
return m_netLastDstId;
}
return 0U;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -130,6 +130,9 @@ namespace nxdn
/// <summary>Helper to change the RCCH verbose state.</summary>
void setRCCHVerbose(bool verbose);
/// <summary>Helper to get the last transmitted destination ID.</summary>
uint32_t getLastDstId() const;
private:
friend class packet::Voice;
packet::Voice* m_voice;

@ -861,6 +861,23 @@ void Control::setDebugVerbose(bool debug, bool verbose)
m_verbose = m_voice->m_verbose = m_data->m_verbose = m_trunk->m_verbose = verbose;
}
/// <summary>
/// Helper to get the last transmitted destination ID.
/// </summary>
/// <returns></returns>
uint32_t Control::getLastDstId() const
{
if (m_rfLastDstId != 0U) {
return m_rfLastDstId;
}
if (m_netLastDstId != 0U) {
return m_netLastDstId;
}
return 0U;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------

@ -139,6 +139,9 @@ namespace p25
/// <summary>Helper to change the debug and verbose state.</summary>
void setDebugVerbose(bool debug, bool verbose);
/// <summary>Helper to get the last transmitted destination ID.</summary>
uint32_t getLastDstId() const;
private:
friend class packet::Voice;
packet::Voice* m_voice;

Loading…
Cancel
Save

Powered by TurnKey Linux.