From 8f7763cac47f73227810993c855130872cd00052 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Thu, 29 Jun 2023 23:03:00 -0400 Subject: [PATCH] initial implementation for a passive monitor tool; correct bad code style; implement feedback of last destination ID; enhance status REST API; --- src/CMakeLists.txt | 57 +++++ src/dmr/Control.cpp | 20 ++ src/dmr/Control.h | 3 + src/dmr/Slot.cpp | 17 ++ src/dmr/Slot.h | 3 + src/host/Host.cpp | 9 + src/host/Host.h | 2 + src/host/setup/ChannelConfigSetWnd.h | 2 +- src/host/setup/FIFOBufferAdjustWnd.h | 2 +- src/host/setup/HSBandwidthAdjustWnd.h | 2 +- src/host/setup/HSGainAdjustWnd.h | 2 +- src/host/setup/LoggingAndDataSetWnd.h | 2 +- src/host/setup/SiteParamSetWnd.h | 2 +- src/host/setup/SymbLevelAdjustWnd.h | 2 +- src/host/setup/SystemConfigSetWnd.h | 2 +- src/monitor/LogDisplayWnd.h | 137 +++++++++++ src/monitor/MonitorApplication.h | 83 +++++++ src/monitor/MonitorMain.cpp | 258 +++++++++++++++++++ src/monitor/MonitorMain.h | 56 +++++ src/monitor/MonitorMainWnd.h | 266 ++++++++++++++++++++ src/monitor/NodeStatusWnd.h | 342 ++++++++++++++++++++++++++ src/network/RESTAPI.cpp | 10 + src/nxdn/Control.cpp | 17 ++ src/nxdn/Control.h | 3 + src/p25/Control.cpp | 17 ++ src/p25/Control.h | 3 + 26 files changed, 1311 insertions(+), 8 deletions(-) create mode 100644 src/monitor/LogDisplayWnd.h create mode 100644 src/monitor/MonitorApplication.h create mode 100644 src/monitor/MonitorMain.cpp create mode 100644 src/monitor/MonitorMain.h create mode 100644 src/monitor/MonitorMainWnd.h create mode 100644 src/monitor/NodeStatusWnd.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 558c8feb..0694507e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/dmr/Control.cpp b/src/dmr/Control.cpp index 9c19a9f2..8cafdba8 100644 --- a/src/dmr/Control.cpp +++ b/src/dmr/Control.cpp @@ -630,6 +630,26 @@ void Control::setCSBKVerbose(bool verbose) lc::CSBK::setVerbose(verbose); } +/// +/// Helper to get the last transmitted destination ID. +/// +/// DMR slot number. +/// +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 // --------------------------------------------------------------------------- diff --git a/src/dmr/Control.h b/src/dmr/Control.h index 499060e8..7dca0794 100644 --- a/src/dmr/Control.h +++ b/src/dmr/Control.h @@ -130,6 +130,9 @@ namespace dmr /// Helper to change the CSBK verbose state. void setCSBKVerbose(bool verbose); + /// Helper to get the last transmitted destination ID. + uint32_t getLastDstId(uint32_t slotNo) const; + private: friend class Slot; diff --git a/src/dmr/Slot.cpp b/src/dmr/Slot.cpp index 65405dbc..1ec500dc 100644 --- a/src/dmr/Slot.cpp +++ b/src/dmr/Slot.cpp @@ -676,6 +676,23 @@ void Slot::setSilenceThreshold(uint32_t threshold) m_silenceThreshold = threshold; } +/// +/// Helper to get the last transmitted destination ID. +/// +/// +uint32_t Slot::getLastDstId() const +{ + if (m_rfLastDstId != 0U) { + return m_rfLastDstId; + } + + if (m_netLastDstId != 0U) { + return m_netLastDstId; + } + + return 0U; +} + /// /// Helper to initialize the DMR slot processor. /// diff --git a/src/dmr/Slot.h b/src/dmr/Slot.h index 8a715e9d..b1fff954 100644 --- a/src/dmr/Slot.h +++ b/src/dmr/Slot.h @@ -121,6 +121,9 @@ namespace dmr /// Helper to set the voice error silence threshold. void setSilenceThreshold(uint32_t threshold); + /// Helper to get the last transmitted destination ID. + uint32_t getLastDstId() const; + /// Helper to initialize the slot processor. 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, diff --git a/src/host/Host.cpp b/src/host/Host.cpp index c7d76c17..559c2340 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -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(); } } } diff --git a/src/host/Host.h b/src/host/Host.h index 3a05780f..813f9925 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -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; diff --git a/src/host/setup/ChannelConfigSetWnd.h b/src/host/setup/ChannelConfigSetWnd.h index 7db4bed4..b3820901 100644 --- a/src/host/setup/ChannelConfigSetWnd.h +++ b/src/host/setup/ChannelConfigSetWnd.h @@ -88,7 +88,7 @@ private: /// /// /// - void initControls() + void initControls() override { yaml::Node rfssConfig = m_setup->m_conf["system"]["config"]; m_setup->m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); diff --git a/src/host/setup/FIFOBufferAdjustWnd.h b/src/host/setup/FIFOBufferAdjustWnd.h index f92a0c6f..c6365f2d 100644 --- a/src/host/setup/FIFOBufferAdjustWnd.h +++ b/src/host/setup/FIFOBufferAdjustWnd.h @@ -77,7 +77,7 @@ private: /// /// /// - void initControls() + void initControls() override { // symbol levels { diff --git a/src/host/setup/HSBandwidthAdjustWnd.h b/src/host/setup/HSBandwidthAdjustWnd.h index 432fef1b..da75bb51 100644 --- a/src/host/setup/HSBandwidthAdjustWnd.h +++ b/src/host/setup/HSBandwidthAdjustWnd.h @@ -82,7 +82,7 @@ private: /// /// /// - void initControls() + void initControls() override { // symbol levels { diff --git a/src/host/setup/HSGainAdjustWnd.h b/src/host/setup/HSGainAdjustWnd.h index 6a7c5de5..309b8cb9 100644 --- a/src/host/setup/HSGainAdjustWnd.h +++ b/src/host/setup/HSGainAdjustWnd.h @@ -85,7 +85,7 @@ private: /// /// /// - void initControls() + void initControls() override { // gain { diff --git a/src/host/setup/LoggingAndDataSetWnd.h b/src/host/setup/LoggingAndDataSetWnd.h index c27387f6..596bbbcd 100644 --- a/src/host/setup/LoggingAndDataSetWnd.h +++ b/src/host/setup/LoggingAndDataSetWnd.h @@ -85,7 +85,7 @@ private: /// /// /// - void initControls() + void initControls() override { yaml::Node logConf = m_setup->m_conf["log"]; uint32_t logLevel = logConf["fileLevel"].as(1U); diff --git a/src/host/setup/SiteParamSetWnd.h b/src/host/setup/SiteParamSetWnd.h index c945248f..57c13cef 100644 --- a/src/host/setup/SiteParamSetWnd.h +++ b/src/host/setup/SiteParamSetWnd.h @@ -105,7 +105,7 @@ private: /// /// /// - void initControls() + void initControls() override { yaml::Node cwId = m_setup->m_conf["system"]["cwId"]; bool enabled = cwId["enable"].as(false); diff --git a/src/host/setup/SymbLevelAdjustWnd.h b/src/host/setup/SymbLevelAdjustWnd.h index 0887f335..b7be84f9 100644 --- a/src/host/setup/SymbLevelAdjustWnd.h +++ b/src/host/setup/SymbLevelAdjustWnd.h @@ -82,7 +82,7 @@ private: /// /// /// - void initControls() + void initControls() override { // symbol levels { diff --git a/src/host/setup/SystemConfigSetWnd.h b/src/host/setup/SystemConfigSetWnd.h index dc20d2b7..3b5af33c 100644 --- a/src/host/setup/SystemConfigSetWnd.h +++ b/src/host/setup/SystemConfigSetWnd.h @@ -99,7 +99,7 @@ private: /// /// /// - 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 diff --git a/src/monitor/LogDisplayWnd.h b/src/monitor/LogDisplayWnd.h new file mode 100644 index 00000000..1b77cfd0 --- /dev/null +++ b/src/monitor/LogDisplayWnd.h @@ -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 +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: + /// + /// Initializes a new instance of the LogDisplayWnd class. + /// + /// + explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget} + { + m_scrollText.ignorePadding(); + + m_timerId = addTimer(250); // starts the timer every 250 milliseconds + } + /// Copy constructor. + LogDisplayWnd(const LogDisplayWnd&) = delete; + /// Move constructor. + LogDisplayWnd(LogDisplayWnd&&) noexcept = delete; + /// Finalizes an instance of the LogDisplayWnd class. + ~LogDisplayWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete; + +private: + FTextView m_scrollText{this}; + int m_timerId; + + /// + /// + /// + 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(); + } + + /// + /// + /// + void adjustSize() override + { + FDialog::adjustSize(); + + m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1}); + + redraw(); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onClose(FCloseEvent* e) override + { + minimizeWindow(); + } + + /// + /// + /// + /// + 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__ \ No newline at end of file diff --git a/src/monitor/MonitorApplication.h b/src/monitor/MonitorApplication.h new file mode 100644 index 00000000..12cb513f --- /dev/null +++ b/src/monitor/MonitorApplication.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 +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the finalcut application. +// --------------------------------------------------------------------------- + +class HOST_SW_API MonitorApplication final : public finalcut::FApplication { +public: + /// + /// Initializes a new instance of the MonitorApplication class. + /// + /// + /// + explicit MonitorApplication(const int& argc, char** argv) : FApplication{argc, argv} + { + m_statusRefreshTimer = addTimer(1000); + } + +protected: + /// + /// + /// + virtual void processExternalUserEvent() + { + /* stub */ + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onTimer(FTimerEvent* timer) override + { + if (timer != nullptr) { + if (timer->getTimerId() == m_statusRefreshTimer) { + /* stub */ + } + } + } + +private: + int m_statusRefreshTimer; +}; + +#endif // __MONITOR_APPLICATION_H__ \ No newline at end of file diff --git a/src/monitor/MonitorMain.cpp b/src/monitor/MonitorMain.cpp new file mode 100644 index 00000000..831d4495 --- /dev/null +++ b/src/monitor/MonitorMain.cpp @@ -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 +* +* 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 +#include +#include +#include + +// --------------------------------------------------------------------------- +// 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 +// --------------------------------------------------------------------------- + +/// +/// Helper to print a fatal error message and exit. +/// +/// This is a variable argument function. +/// Message. +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); +} + +/// +/// Helper to pring usage the command line arguments. (And optionally an error.) +/// +/// Error message. +/// Error message arguments. +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 ]\n\n" + " -c 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); +} + +/// +/// Helper to validate the command line arguments. +/// +/// Argument count. +/// Array of argument strings. +/// Count of remaining unprocessed 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 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(); + uint32_t idenReloadTime = systemConf["iden_table"]["time"].as(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(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; +} diff --git a/src/monitor/MonitorMain.h b/src/monitor/MonitorMain.h new file mode 100644 index 00000000..711c10c1 --- /dev/null +++ b/src/monitor/MonitorMain.h @@ -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 + +// --------------------------------------------------------------------------- +// 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__ diff --git a/src/monitor/MonitorMainWnd.h b/src/monitor/MonitorMainWnd.h new file mode 100644 index 00000000..60fb9ffb --- /dev/null +++ b/src/monitor/MonitorMainWnd.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 +using namespace finalcut; +#undef null + +#include "monitor/MonitorMain.h" + +#include "monitor/LogDisplayWnd.h" +#include "monitor/NodeStatusWnd.h" + +#include + +// --------------------------------------------------------------------------- +// 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: + /// + /// Initializes a new instance of the MonitorMainWnd class. + /// + /// + 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 m_nodes; + + std::vector m_voiceChNo; + std::unordered_map 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}; + + /// + /// + /// + 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(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("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("127.0.0.1"); + std::string restApiAddress = std::string("127.0.0.1"); + uint16_t restApiPort = (uint16_t)networkConf["restPort"].as(REST_API_DEFAULT_PORT); + std::string restApiPassword = networkConf["restPassword"].as(); + + 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("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("127.0.0.1"); + uint16_t restApiPort = (uint16_t)channel["restPort"].as(REST_API_DEFAULT_PORT); + std::string restApiPassword = channel["restPassword"].as(); + + ::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 + */ + + /// + /// + /// + /// + void onShow(FShowEvent* e) override + { + intializeNodeDisplay(); + } + + /// + /// + /// + /// + void onClose(FCloseEvent* e) override + { + FApplication::closeConfirmationDialog(this, e); + } +}; + +#endif // __MONITOR_WND_H__ \ No newline at end of file diff --git a/src/monitor/NodeStatusWnd.h b/src/monitor/NodeStatusWnd.h new file mode 100644 index 00000000..24ead7b3 --- /dev/null +++ b/src/monitor/NodeStatusWnd.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 +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: + /// + /// Initializes a new instance of the NodeStatusWnd class. + /// + /// + 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 + } + /// Copy constructor. + NodeStatusWnd(const NodeStatusWnd&) = delete; + /// Move constructor. + NodeStatusWnd(NodeStatusWnd&&) noexcept = delete; + /// Finalizes an instance of the NodeStatusWnd class. + ~NodeStatusWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const NodeStatusWnd&) -> NodeStatusWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (NodeStatusWnd&&) noexcept -> NodeStatusWnd& = delete; + + /// Disable set X coordinate. + void setX(int, bool = true) override { } + /// Disable set Y coordinate. + void setY(int, bool = true) override { } + /// Disable set position. + void setPos(const FPoint&, bool = true) override { } + + /// Sets the channel data. + void setChData(lookups::VoiceChData chData) { m_chData = chData; } + /// Sets the channel number. + void setChannelId(uint8_t channelId) { m_channelId = channelId; } + /// Sets the channel number. + 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}; + + /// + /// + /// + 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(); + } + + /// + /// + /// + 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})); + } + + /// + /// + /// + 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"); + } + } + + /// + /// + /// + /// + 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() && rsp["p25CtrlEnable"].is() && + rsp["nxdnCtrlEnable"].is()) { + bool dmrTSCCEnable = rsp["dmrTSCCEnable"].get(); + bool dmrCC = rsp["dmrCC"].get(); + bool p25CtrlEnable = rsp["p25CtrlEnable"].get(); + bool p25CC = rsp["p25CC"].get(); + bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get(); + bool nxdnCC = rsp["nxdnCC"].get(); + + // 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()) { + m_tx = rsp["tx"].get(); + } + else { + ::LogWarning(LOG_HOST, "%s:%u, does not report Tx status"); + m_tx = false; + } + } + } + + // get the remote node channel information + if (rsp["channelId"].is() && rsp["channelNo"].is()) { + uint8_t channelId = rsp["channelId"].get(); + uint32_t channelNo = rsp["channelNo"].get(); + 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 lastDstId = rsp["lastDstId"].get(); + 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__ \ No newline at end of file diff --git a/src/network/RESTAPI.cpp b/src/network/RESTAPI.cpp index 76363366..d6b35855 100644 --- a/src/network/RESTAPI.cpp +++ b/src/network/RESTAPI.cpp @@ -508,8 +508,11 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, uint8_t protoVer = m_host->m_modem->getVersion(); response["protoVer"].set(protoVer); + response["dmrTSCCEnable"].set(m_host->m_dmrTSCCData); response["dmrCC"].set(m_host->m_dmrCtrlChannel); + response["p25CtrlEnable"].set(m_host->m_p25CCData); response["p25CC"].set(m_host->m_p25CtrlChannel); + response["nxdnCtrlEnable"].set(m_host->m_nxdnCCData); response["nxdnCC"].set(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(p25VOC); response["nxdnVOC"].set(nxdnVOC); + + response["tx"].set(m_host->m_modem->m_tx); + + response["channelId"].set(m_host->m_channelId); + response["channelNo"].set(m_host->m_channelNo); + + response["lastDstId"].set(m_host->m_lastDstId); } { diff --git a/src/nxdn/Control.cpp b/src/nxdn/Control.cpp index cb0f3f3b..8129d55d 100644 --- a/src/nxdn/Control.cpp +++ b/src/nxdn/Control.cpp @@ -748,6 +748,23 @@ void Control::setRCCHVerbose(bool verbose) lc::RTCH::setVerbose(verbose); } +/// +/// Helper to get the last transmitted destination ID. +/// +/// +uint32_t Control::getLastDstId() const +{ + if (m_rfLastDstId != 0U) { + return m_rfLastDstId; + } + + if (m_netLastDstId != 0U) { + return m_netLastDstId; + } + + return 0U; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/nxdn/Control.h b/src/nxdn/Control.h index 59ef27c4..8a28be80 100644 --- a/src/nxdn/Control.h +++ b/src/nxdn/Control.h @@ -130,6 +130,9 @@ namespace nxdn /// Helper to change the RCCH verbose state. void setRCCHVerbose(bool verbose); + /// Helper to get the last transmitted destination ID. + uint32_t getLastDstId() const; + private: friend class packet::Voice; packet::Voice* m_voice; diff --git a/src/p25/Control.cpp b/src/p25/Control.cpp index 4d07ed6e..9e1d3bff 100644 --- a/src/p25/Control.cpp +++ b/src/p25/Control.cpp @@ -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; } +/// +/// Helper to get the last transmitted destination ID. +/// +/// +uint32_t Control::getLastDstId() const +{ + if (m_rfLastDstId != 0U) { + return m_rfLastDstId; + } + + if (m_netLastDstId != 0U) { + return m_netLastDstId; + } + + return 0U; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/p25/Control.h b/src/p25/Control.h index 8dac6047..ce9fb32b 100644 --- a/src/p25/Control.h +++ b/src/p25/Control.h @@ -139,6 +139,9 @@ namespace p25 /// Helper to change the debug and verbose state. void setDebugVerbose(bool debug, bool verbose); + /// Helper to get the last transmitted destination ID. + uint32_t getLastDstId() const; + private: friend class packet::Voice; packet::Voice* m_voice;