From d45af90c07041d04cc4adc6257e5d01a547aa34b Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 30 Jun 2023 13:55:23 -0400 Subject: [PATCH] initial bare-bones implementation of dvmmon; enhance REST API to return more detailed status information (for use by dvmmon); cleanup file code style; --- configs/monitor-config.example.yml | 25 +++ src/host/setup/AdjustWndBase.h | 3 +- src/host/setup/BERDisplayWnd.h | 3 +- src/host/setup/ChannelConfigSetWnd.h | 3 +- src/host/setup/CloseWndBase.h | 3 +- src/host/setup/FIFOBufferAdjustWnd.h | 3 +- src/host/setup/HSBandwidthAdjustWnd.h | 3 +- src/host/setup/HSGainAdjustWnd.h | 3 +- src/host/setup/LevelAdjustWnd.h | 3 +- src/host/setup/LogDisplayWnd.h | 3 +- src/host/setup/LoggingAndDataSetWnd.h | 3 +- src/host/setup/ModemStatusWnd.h | 3 +- src/host/setup/SiteParamSetWnd.h | 3 +- src/host/setup/SymbLevelAdjustWnd.h | 3 +- src/host/setup/SystemConfigSetWnd.h | 3 +- src/monitor/InhibitSubscriberWnd.h | 144 +++++++++++++ src/monitor/LogDisplayWnd.h | 3 +- src/monitor/MonitorMain.cpp | 27 ++- src/monitor/MonitorMain.h | 1 - src/monitor/MonitorMainWnd.h | 144 ++++++------- src/monitor/NodeStatusWnd.h | 229 +++++++++++---------- src/monitor/PageSubscriberWnd.h | 144 +++++++++++++ src/monitor/RadioCheckSubscriberWnd.h | 144 +++++++++++++ src/monitor/SelectedNodeWnd.h | 124 +++++++++++ src/monitor/TransmitWndBase.h | 285 ++++++++++++++++++++++++++ src/monitor/UninhibitSubscriberWnd.h | 144 +++++++++++++ src/network/RESTAPI.cpp | 2 + 27 files changed, 1227 insertions(+), 231 deletions(-) create mode 100644 configs/monitor-config.example.yml create mode 100644 src/monitor/InhibitSubscriberWnd.h create mode 100644 src/monitor/PageSubscriberWnd.h create mode 100644 src/monitor/RadioCheckSubscriberWnd.h create mode 100644 src/monitor/SelectedNodeWnd.h create mode 100644 src/monitor/TransmitWndBase.h create mode 100644 src/monitor/UninhibitSubscriberWnd.h diff --git a/configs/monitor-config.example.yml b/configs/monitor-config.example.yml new file mode 100644 index 00000000..78452b5f --- /dev/null +++ b/configs/monitor-config.example.yml @@ -0,0 +1,25 @@ +# +# Digital Voice Modem - Monitor Configuration +# +# @package DVM / Monitor +# + +# +# 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 + +# +# DVM Channels to Monitor +# +channels: + # REST API IP Address for channel. + - restAddress: 127.0.0.1 + # REST API Port number for channel. + restPort: 9990 + # REST API access password for channel. + restPassword: "PASSWORD" \ No newline at end of file diff --git a/src/host/setup/AdjustWndBase.h b/src/host/setup/AdjustWndBase.h index 00d38b82..8a7dd73d 100644 --- a/src/host/setup/AdjustWndBase.h +++ b/src/host/setup/AdjustWndBase.h @@ -37,8 +37,7 @@ using namespace finalcut; // This class implements the base class for adjustment windows. // --------------------------------------------------------------------------- -class HOST_SW_API AdjustWndBase : public finalcut::FDialog -{ +class HOST_SW_API AdjustWndBase : public finalcut::FDialog { public: /// /// Initializes a new instance of the AdjustWndBase class. diff --git a/src/host/setup/BERDisplayWnd.h b/src/host/setup/BERDisplayWnd.h index 1fa36281..50df7837 100644 --- a/src/host/setup/BERDisplayWnd.h +++ b/src/host/setup/BERDisplayWnd.h @@ -45,8 +45,7 @@ using namespace finalcut; // This class implements the bit error rate display window. // --------------------------------------------------------------------------- -class HOST_SW_API BERDisplayWnd final : public finalcut::FDialog -{ +class HOST_SW_API BERDisplayWnd final : public finalcut::FDialog { public: /// /// Initializes a new instance of the BERDisplayWnd class. diff --git a/src/host/setup/ChannelConfigSetWnd.h b/src/host/setup/ChannelConfigSetWnd.h index b3820901..addb339f 100644 --- a/src/host/setup/ChannelConfigSetWnd.h +++ b/src/host/setup/ChannelConfigSetWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the channel configuration window. // --------------------------------------------------------------------------- -class HOST_SW_API ChannelConfigSetWnd final : public CloseWndBase -{ +class HOST_SW_API ChannelConfigSetWnd final : public CloseWndBase { public: /// /// Initializes a new instance of the ChannelConfigSetWnd class. diff --git a/src/host/setup/CloseWndBase.h b/src/host/setup/CloseWndBase.h index b9ed282c..9f43bbf8 100644 --- a/src/host/setup/CloseWndBase.h +++ b/src/host/setup/CloseWndBase.h @@ -37,8 +37,7 @@ using namespace finalcut; // This class implements the base class for windows with close buttons. // --------------------------------------------------------------------------- -class HOST_SW_API CloseWndBase : public finalcut::FDialog -{ +class HOST_SW_API CloseWndBase : public finalcut::FDialog { public: /// /// Initializes a new instance of the CloseWndBase class. diff --git a/src/host/setup/FIFOBufferAdjustWnd.h b/src/host/setup/FIFOBufferAdjustWnd.h index c6365f2d..e59d81fa 100644 --- a/src/host/setup/FIFOBufferAdjustWnd.h +++ b/src/host/setup/FIFOBufferAdjustWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the FIFO buffer adjustment window. // --------------------------------------------------------------------------- -class HOST_SW_API FIFOBufferAdjustWnd final : public CloseWndBase -{ +class HOST_SW_API FIFOBufferAdjustWnd final : public CloseWndBase { public: /// /// Initializes a new instance of the FIFOBufferAdjustWnd class. diff --git a/src/host/setup/HSBandwidthAdjustWnd.h b/src/host/setup/HSBandwidthAdjustWnd.h index da75bb51..0e13215b 100644 --- a/src/host/setup/HSBandwidthAdjustWnd.h +++ b/src/host/setup/HSBandwidthAdjustWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the hotspot bandwidth adjustment window. // --------------------------------------------------------------------------- -class HOST_SW_API HSBandwidthAdjustWnd final : public AdjustWndBase -{ +class HOST_SW_API HSBandwidthAdjustWnd final : public AdjustWndBase { public: /// /// Initializes a new instance of the HSBandwidthAdjustWnd class. diff --git a/src/host/setup/HSGainAdjustWnd.h b/src/host/setup/HSGainAdjustWnd.h index 309b8cb9..f9e97d2a 100644 --- a/src/host/setup/HSGainAdjustWnd.h +++ b/src/host/setup/HSGainAdjustWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the hotspot gain adjustment window. // --------------------------------------------------------------------------- -class HOST_SW_API HSGainAdjustWnd final : public AdjustWndBase -{ +class HOST_SW_API HSGainAdjustWnd final : public AdjustWndBase { public: /// /// Initializes a new instance of the HSGainAdjustWnd class. diff --git a/src/host/setup/LevelAdjustWnd.h b/src/host/setup/LevelAdjustWnd.h index 7ccc1e8e..b70559a0 100644 --- a/src/host/setup/LevelAdjustWnd.h +++ b/src/host/setup/LevelAdjustWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the modem level adjustment window. // --------------------------------------------------------------------------- -class HOST_SW_API LevelAdjustWnd final : public AdjustWndBase -{ +class HOST_SW_API LevelAdjustWnd final : public AdjustWndBase { public: /// /// Initializes a new instance of the LevelAdjustWnd class. diff --git a/src/host/setup/LogDisplayWnd.h b/src/host/setup/LogDisplayWnd.h index c599d68e..56867fb3 100644 --- a/src/host/setup/LogDisplayWnd.h +++ b/src/host/setup/LogDisplayWnd.h @@ -36,8 +36,7 @@ using namespace finalcut; // This class implements the log display window. // --------------------------------------------------------------------------- -class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream -{ +class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream { public: /// /// Initializes a new instance of the LogDisplayWnd class. diff --git a/src/host/setup/LoggingAndDataSetWnd.h b/src/host/setup/LoggingAndDataSetWnd.h index 596bbbcd..1df5a18a 100644 --- a/src/host/setup/LoggingAndDataSetWnd.h +++ b/src/host/setup/LoggingAndDataSetWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the logging and data configuration window. // --------------------------------------------------------------------------- -class HOST_SW_API LoggingAndDataSetWnd final : public CloseWndBase -{ +class HOST_SW_API LoggingAndDataSetWnd final : public CloseWndBase { public: /// /// Initializes a new instance of the LoggingAndDataSetWnd class. diff --git a/src/host/setup/ModemStatusWnd.h b/src/host/setup/ModemStatusWnd.h index c50fc692..b4570a7f 100644 --- a/src/host/setup/ModemStatusWnd.h +++ b/src/host/setup/ModemStatusWnd.h @@ -36,8 +36,7 @@ using namespace finalcut; // This class implements the modem status display window. // --------------------------------------------------------------------------- -class HOST_SW_API ModemStatusWnd final : public finalcut::FDialog -{ +class HOST_SW_API ModemStatusWnd final : public finalcut::FDialog { public: /// /// Initializes a new instance of the ModemStatusWnd class. diff --git a/src/host/setup/SiteParamSetWnd.h b/src/host/setup/SiteParamSetWnd.h index 57c13cef..150dd9b1 100644 --- a/src/host/setup/SiteParamSetWnd.h +++ b/src/host/setup/SiteParamSetWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the site parameters configuration window. // --------------------------------------------------------------------------- -class HOST_SW_API SiteParamSetWnd final : public CloseWndBase -{ +class HOST_SW_API SiteParamSetWnd final : public CloseWndBase { public: /// /// Initializes a new instance of the SiteParamSetWnd class. diff --git a/src/host/setup/SymbLevelAdjustWnd.h b/src/host/setup/SymbLevelAdjustWnd.h index b7be84f9..60a3c309 100644 --- a/src/host/setup/SymbLevelAdjustWnd.h +++ b/src/host/setup/SymbLevelAdjustWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the symbol level adjustment window. // --------------------------------------------------------------------------- -class HOST_SW_API SymbLevelAdjustWnd final : public AdjustWndBase -{ +class HOST_SW_API SymbLevelAdjustWnd final : public AdjustWndBase { public: /// /// Initializes a new instance of the SymbLevelAdjustWnd class. diff --git a/src/host/setup/SystemConfigSetWnd.h b/src/host/setup/SystemConfigSetWnd.h index 3b5af33c..74d97d62 100644 --- a/src/host/setup/SystemConfigSetWnd.h +++ b/src/host/setup/SystemConfigSetWnd.h @@ -39,8 +39,7 @@ using namespace finalcut; // This class implements the system configuration window. // --------------------------------------------------------------------------- -class HOST_SW_API SystemConfigSetWnd final : public CloseWndBase -{ +class HOST_SW_API SystemConfigSetWnd final : public CloseWndBase { public: /// /// Initializes a new instance of the SystemConfigSetWnd class. diff --git a/src/monitor/InhibitSubscriberWnd.h b/src/monitor/InhibitSubscriberWnd.h new file mode 100644 index 00000000..540a76bd --- /dev/null +++ b/src/monitor/InhibitSubscriberWnd.h @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* 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(__INHIBIT_SUBSCRIBER_WND_H__) +#define __INHIBIT_SUBSCRIBER_WND_H__ + +#include "monitor/TransmitWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the inhibit subscriber window. +// --------------------------------------------------------------------------- + +class HOST_SW_API InhibitSubscriberWnd final : public TransmitWndBase { +public: + /// + /// Initializes a new instance of the InhibitSubscriberWnd class. + /// + /// + /// + explicit InhibitSubscriberWnd(lookups::VoiceChData channel, FWidget* widget = nullptr) : TransmitWndBase{channel, widget} + { + /* stub */ + } + +private: + FLabel m_dialogLabel{"Inhibit Subscriber", this}; + + FLabel m_subscriberLabel{"Subscriber ID: ", this}; + FSpinBox m_subscriber{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Inhibit Subscriber"); + FDialog::setSize(FSize{60, 16}); + + TransmitWndBase::initLayout(); + } + + /// + /// + /// + void initControls() override + { + TransmitWndBase::initControls(); + + if (m_hideModeSelect) { + FDialog::setSize(FSize{60, 12}); + resizeControls(); + } + + // subscriber entry + { + if (!m_hideModeSelect) { + m_dialogLabel.setGeometry(FPoint(2, 6), FSize(20, 2)); + } + else { + m_dialogLabel.setGeometry(FPoint(2, 2), FSize(20, 2)); + } + m_dialogLabel.setEmphasis(); + m_dialogLabel.setAlignment(Align::Center); + + if (!m_hideModeSelect) { + m_subscriberLabel.setGeometry(FPoint(2, 8), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 8), FSize(20, 1)); + } + else { + m_subscriberLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 4), FSize(20, 1)); + } + m_subscriber.setRange(1, 16777211); + m_subscriber.setValue(1); + m_subscriber.setShadow(false); + } + + m_subscriberLabel.redraw(); + redraw(); + } + + /// + /// + /// + void setTransmit() override + { + std::string method = PUT_DMR_RID; + json::object req = json::object(); + req["command"].set(RID_CMD_INHIBIT); + uint32_t dstId = m_subscriber.getValue(); + req["dstId"].set(dstId); + + switch (m_mode) { + case modem::STATE_DMR: + { + uint8_t slot = m_dmrSlot.getValue(); + req["slot"].set(slot); + } + break; + case modem::STATE_P25: + { + method = PUT_P25_RID; + } + break; + case modem::STATE_NXDN: + return; + } + + // callback REST API + int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), + HTTP_PUT, method, req, g_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_HOST, "failed to send request to %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port()); + } + } +}; + +#endif // __INHIBIT_SUBSCRIBER_WND_H__ \ No newline at end of file diff --git a/src/monitor/LogDisplayWnd.h b/src/monitor/LogDisplayWnd.h index 1b77cfd0..07af8660 100644 --- a/src/monitor/LogDisplayWnd.h +++ b/src/monitor/LogDisplayWnd.h @@ -34,8 +34,7 @@ using namespace finalcut; // This class implements the log display window. // --------------------------------------------------------------------------- -class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream -{ +class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream { public: /// /// Initializes a new instance of the LogDisplayWnd class. diff --git a/src/monitor/MonitorMain.cpp b/src/monitor/MonitorMain.cpp index 831d4495..d3f72bd0 100644 --- a/src/monitor/MonitorMain.cpp +++ b/src/monitor/MonitorMain.cpp @@ -54,7 +54,6 @@ yaml::Node g_conf; bool g_debug = false; lookups::IdenTableLookup* g_idenTable = nullptr; -uint32_t g_channelId = 0U; // --------------------------------------------------------------------------- // Global Functions @@ -97,8 +96,8 @@ void usage(const char* message, const char* arg) ::fprintf(stderr, "\n\n"); } - ::fprintf(stdout, "usage: %s [-dvh] [-c ]\n\n" - " -c specifies the configuration file to use\n" + ::fprintf(stdout, "usage: %s [-dvh] [-c ]\n\n" + " -c specifies the monitor configuration file to use\n" "\n" " -d enable debug\n" " -v show version information\n" @@ -135,11 +134,11 @@ int checkArgs(int argc, char* argv[]) } else if (IS("-c")) { if (argc-- <= 0) - usage("error: %s", "must specify the configuration file to use"); + usage("error: %s", "must specify the monitor configuration file to use"); g_iniFile = std::string(argv[++i]); if (g_iniFile == "") - usage("error: %s", "configuration file cannot be blank!"); + usage("error: %s", "monitor configuration file cannot be blank!"); p += 2; } @@ -218,17 +217,21 @@ int main(int argc, char** 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); + std::string idenLookupFile = g_conf["iden_table"]["file"].as(); + uint32_t idenReloadTime = g_conf["iden_table"]["time"].as(0U); if (idenLookupFile.length() <= 0U) { ::LogError(LOG_HOST, "No bandplan identity table? This must be defined!"); return 1; } + yaml::Node& voiceChList = g_conf["channels"]; + if (voiceChList.size() == 0U) { + ::LogError(LOG_HOST, "No channels defined to monitor? This must be defined!"); + return 1; + } + g_logDisplayLevel = 0U; LogInfo("Iden Table Lookups"); @@ -239,12 +242,6 @@ int main(int argc, char** argv) 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(); diff --git a/src/monitor/MonitorMain.h b/src/monitor/MonitorMain.h index 711c10c1..4ee9d2fe 100644 --- a/src/monitor/MonitorMain.h +++ b/src/monitor/MonitorMain.h @@ -51,6 +51,5 @@ 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 index 8136e35f..a787cfb9 100644 --- a/src/monitor/MonitorMainWnd.h +++ b/src/monitor/MonitorMainWnd.h @@ -40,6 +40,11 @@ using namespace finalcut; #include "monitor/LogDisplayWnd.h" #include "monitor/NodeStatusWnd.h" +#include "monitor/SelectedNodeWnd.h" +#include "monitor/PageSubscriberWnd.h" +#include "monitor/RadioCheckSubscriberWnd.h" +#include "monitor/InhibitSubscriberWnd.h" +#include "monitor/UninhibitSubscriberWnd.h" #include @@ -70,12 +75,35 @@ public: m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); // command menu + m_pageSU.addCallback("clicked", this, [&]() { + PageSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); + m_keyF5.addCallback("activate", this, [&]() { + PageSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); + m_radioCheckSU.addCallback("clicked", this, [&]() { + RadioCheckSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); m_cmdMenuSeparator1.setSeparator(); - m_cmdMenuSeparator2.setSeparator(); - - // engineering menu - m_engineeringMenuSeparator1.setSeparator(); - m_engineeringMenuSeparator2.setSeparator(); + m_inhibitSU.addCallback("clicked", this, [&]() { + InhibitSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); + m_keyF7.addCallback("activate", this, [&]() { + InhibitSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); + m_uninhibitSU.addCallback("clicked", this, [&]() { + UninhibitSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); + m_keyF8.addCallback("activate", this, [&]() { + UninhibitSubscriberWnd wnd{m_selectedCh, this}; + wnd.show(); + }); // help menu m_aboutItem.addCallback("clicked", this, [&]() { @@ -90,14 +118,17 @@ public: }); } + /// + lookups::VoiceChData getSelectedCh() { return m_selectedCh; } + private: friend class MonitorApplication; LogDisplayWnd m_logWnd{this}; + SelectedNodeWnd m_selectWnd{this}; std::vector m_nodes; - std::vector m_voiceChNo; - std::unordered_map m_voiceChData; + lookups::VoiceChData m_selectedCh; FString m_line{13, UniChar::BoxDrawingsHorizontal}; @@ -112,25 +143,15 @@ private: 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}; + FStatusKey m_keyF5{FKey::F5, "Page Subscriber", &m_statusBar}; + FStatusKey m_keyF7{FKey::F7, "Inhibit Subscriber", &m_statusBar}; + FStatusKey m_keyF8{FKey::F8, "Uninhibit Subscriber", &m_statusBar}; /// /// @@ -139,88 +160,32 @@ private: { const auto& rootWidget = getRootWidget(); const int defaultOffsX = 2; - int offsX = defaultOffsX, offsY = 2; + int offsX = defaultOffsX, offsY = 8; 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"); - if (restApiAddress == "0.0.0.0") { - 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 + ** Channels */ - yaml::Node& voiceChList = rfssConfig["voiceChNo"]; + yaml::Node& voiceChList = g_conf["channels"]; 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); + ::LogInfoEx(LOG_HOST, "Channel REST API Adddress %s:%u", restApiAddress.c_str(), restApiPort); - VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword); + VoiceChData data = VoiceChData(0U, 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) { @@ -229,6 +194,16 @@ private: } wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT}); + + wnd->addCallback("update-selected", this, [&](NodeStatusWnd* wnd) { + std::stringstream ss; + ss << (uint32_t)(wnd->getChannelId()) << "-" << wnd->getChannelNo() << " / " + << wnd->getChData().address() << ":" << wnd->getChData().port(); + + m_selectWnd.setSelectedText(ss.str()); + m_selectedCh = wnd->getChData(); + }, wnd); + offsX += NODE_STATUS_WIDTH + 2; m_nodes.push_back(wnd); } @@ -238,8 +213,15 @@ private: for (auto* wnd : m_nodes) { wnd->setModal(false); wnd->show(); + + wnd->lowerWindow(); + wnd->deactivateWindow(); } + // raise and activate first window + m_nodes.at(0)->raiseWindow(); + m_nodes.at(0)->activateWindow(); + redraw(); } diff --git a/src/monitor/NodeStatusWnd.h b/src/monitor/NodeStatusWnd.h index 9b02cfb5..1278cda0 100644 --- a/src/monitor/NodeStatusWnd.h +++ b/src/monitor/NodeStatusWnd.h @@ -48,8 +48,7 @@ using namespace finalcut; // This class implements the node status display window. // --------------------------------------------------------------------------- -class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog -{ +class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog { public: /// /// Initializes a new instance of the NodeStatusWnd class. @@ -79,12 +78,14 @@ public: /// Disable set position. void setPos(const FPoint&, bool = true) override { } + /// Gets the channel ID. + uint8_t getChannelId() const { return m_channelId; } + /// Gets the channel number. + uint32_t getChannelNo() const { return m_channelNo; } + /// Gets the channel data. + lookups::VoiceChData getChData() { return m_chData; } /// 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; @@ -162,41 +163,18 @@ private: 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)); + m_chanNo.setText(""); } // 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_txFreq.setText(""); 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()); + m_rxFreq.setText(""); } // last TG @@ -208,6 +186,57 @@ private: } } + /// + /// + /// + void calculateRxTx() + { + 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); + } + + m_chanNo.setText(__INT_STR(m_channelId) + "-" + __INT_STR(m_channelNo)); + + 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))); + + std::stringstream ss; + ss << std::fixed << std::setprecision(4) << (float)(txFrequency / 1000000.0f); + + m_txFreq.setText(ss.str()); + + ss.str(std::string()); + ss << std::fixed << std::setprecision(4) << (float)(rxFrequency / 1000000.0f); + + m_rxFreq.setText(ss.str()); + + if (isWindowActive()) { + emitCallback("update-selected"); + } + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + void onWindowRaised(FEvent* e) override + { + FDialog::onWindowLowered(e); + emitCallback("update-selected"); + } + /// /// /// @@ -230,91 +259,83 @@ private: 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 p25VOC = rsp["p25VOC"].get(); - bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get(); - bool nxdnCC = rsp["nxdnCC"].get(); - - // are we a dedicated control channel? - if (dmrCC || p25CC || nxdnCC) { - m_control = true; - if (p25CC && p25VOC) { - setText("CONTROL (VOC)"); - } - else { - 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"); + try { + // 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 p25VOC = rsp["p25VOC"].get(); + bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get(); + bool nxdnCC = rsp["nxdnCC"].get(); + + // are we a dedicated control channel? + if (dmrCC || p25CC || nxdnCC) { + m_control = true; + if (p25CC && p25VOC) { + setText("CONTROL (VOC)"); + } + else { + setText("CONTROL"); + } } - // 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; + // 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; - } + // 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(); - // 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; - } + if (m_channelId != channelId && m_channelNo != channelNo) { + m_channelId = channelId; + m_channelNo = channelNo; - // reset controls for our display if necessary - if (resetControls) { - initControls(); - setText(getText() + "*"); + calculateRxTx(); + } + } + else { + ::LogWarning(LOG_HOST, "%s:%u, does not report channel information"); } - } - 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"); + // 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 { - m_lastTG.setText(__INT_STR(lastDstId)); + ::LogWarning(LOG_HOST, "%s:%u, does not report last TG information"); } } - else { - ::LogWarning(LOG_HOST, "%s:%u, does not report last TG information"); + catch (std::exception&) { + ::LogWarning(LOG_HOST, "%s:%u, failed to properly handle status"); + m_failed = true; } } } diff --git a/src/monitor/PageSubscriberWnd.h b/src/monitor/PageSubscriberWnd.h new file mode 100644 index 00000000..294dc2fe --- /dev/null +++ b/src/monitor/PageSubscriberWnd.h @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* 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(__PAGE_SUBSCRIBER_WND_H__) +#define __PAGE_SUBSCRIBER_WND_H__ + +#include "monitor/TransmitWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the page subscriber window. +// --------------------------------------------------------------------------- + +class HOST_SW_API PageSubscriberWnd final : public TransmitWndBase { +public: + /// + /// Initializes a new instance of the PageSubscriberWnd class. + /// + /// + /// + explicit PageSubscriberWnd(lookups::VoiceChData channel, FWidget* widget = nullptr) : TransmitWndBase{channel, widget} + { + /* stub */ + } + +private: + FLabel m_dialogLabel{"Page Subscriber", this}; + + FLabel m_subscriberLabel{"Subscriber ID: ", this}; + FSpinBox m_subscriber{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Page Subscriber"); + FDialog::setSize(FSize{60, 16}); + + TransmitWndBase::initLayout(); + } + + /// + /// + /// + void initControls() override + { + TransmitWndBase::initControls(); + + if (m_hideModeSelect) { + FDialog::setSize(FSize{60, 12}); + resizeControls(); + } + + // subscriber entry + { + if (!m_hideModeSelect) { + m_dialogLabel.setGeometry(FPoint(2, 6), FSize(20, 2)); + } + else { + m_dialogLabel.setGeometry(FPoint(2, 2), FSize(20, 2)); + } + m_dialogLabel.setEmphasis(); + m_dialogLabel.setAlignment(Align::Center); + + if (!m_hideModeSelect) { + m_subscriberLabel.setGeometry(FPoint(2, 8), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 8), FSize(20, 1)); + } + else { + m_subscriberLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 4), FSize(20, 1)); + } + m_subscriber.setRange(1, 16777211); + m_subscriber.setValue(1); + m_subscriber.setShadow(false); + } + + m_subscriberLabel.redraw(); + redraw(); + } + + /// + /// + /// + void setTransmit() override + { + std::string method = PUT_DMR_RID; + json::object req = json::object(); + req["command"].set(RID_CMD_PAGE); + uint32_t dstId = m_subscriber.getValue(); + req["dstId"].set(dstId); + + switch (m_mode) { + case modem::STATE_DMR: + { + uint8_t slot = m_dmrSlot.getValue(); + req["slot"].set(slot); + } + break; + case modem::STATE_P25: + { + method = PUT_P25_RID; + } + break; + case modem::STATE_NXDN: + return; + } + + // callback REST API + int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), + HTTP_PUT, method, req, g_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_HOST, "failed to send request to %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port()); + } + } +}; + +#endif // __PAGE_SUBSCRIBER_WND_H__ \ No newline at end of file diff --git a/src/monitor/RadioCheckSubscriberWnd.h b/src/monitor/RadioCheckSubscriberWnd.h new file mode 100644 index 00000000..5a052c11 --- /dev/null +++ b/src/monitor/RadioCheckSubscriberWnd.h @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* 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(__RADIO_CHECK_SUBSCRIBER_WND_H__) +#define __RADIO_CHECK_SUBSCRIBER_WND_H__ + +#include "monitor/TransmitWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the radio check subscriber window. +// --------------------------------------------------------------------------- + +class HOST_SW_API RadioCheckSubscriberWnd final : public TransmitWndBase { +public: + /// + /// Initializes a new instance of the RadioCheckSubscriberWnd class. + /// + /// + /// + explicit RadioCheckSubscriberWnd(lookups::VoiceChData channel, FWidget* widget = nullptr) : TransmitWndBase{channel, widget} + { + /* stub */ + } + +private: + FLabel m_dialogLabel{"Radio Check Subscriber", this}; + + FLabel m_subscriberLabel{"Subscriber ID: ", this}; + FSpinBox m_subscriber{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Radio Check Subscriber"); + FDialog::setSize(FSize{60, 16}); + + TransmitWndBase::initLayout(); + } + + /// + /// + /// + void initControls() override + { + TransmitWndBase::initControls(); + + if (m_hideModeSelect) { + FDialog::setSize(FSize{60, 12}); + resizeControls(); + } + + // subscriber entry + { + if (!m_hideModeSelect) { + m_dialogLabel.setGeometry(FPoint(2, 6), FSize(20, 2)); + } + else { + m_dialogLabel.setGeometry(FPoint(2, 2), FSize(20, 2)); + } + m_dialogLabel.setEmphasis(); + m_dialogLabel.setAlignment(Align::Center); + + if (!m_hideModeSelect) { + m_subscriberLabel.setGeometry(FPoint(2, 8), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 8), FSize(20, 1)); + } + else { + m_subscriberLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 4), FSize(20, 1)); + } + m_subscriber.setRange(1, 16777211); + m_subscriber.setValue(1); + m_subscriber.setShadow(false); + } + + m_subscriberLabel.redraw(); + redraw(); + } + + /// + /// + /// + void setTransmit() override + { + std::string method = PUT_DMR_RID; + json::object req = json::object(); + req["command"].set(RID_CMD_CHECK); + uint32_t dstId = m_subscriber.getValue(); + req["dstId"].set(dstId); + + switch (m_mode) { + case modem::STATE_DMR: + { + uint8_t slot = m_dmrSlot.getValue(); + req["slot"].set(slot); + } + break; + case modem::STATE_P25: + { + method = PUT_P25_RID; + } + break; + case modem::STATE_NXDN: + return; + } + + // callback REST API + int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), + HTTP_PUT, method, req, g_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_HOST, "failed to send request to %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port()); + } + } +}; + +#endif // __RADIO_CHECK_SUBSCRIBER_WND_H__ \ No newline at end of file diff --git a/src/monitor/SelectedNodeWnd.h b/src/monitor/SelectedNodeWnd.h new file mode 100644 index 00000000..8353dd6a --- /dev/null +++ b/src/monitor/SelectedNodeWnd.h @@ -0,0 +1,124 @@ +/** +* 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(__SELECTED_NODE_WND_H__) +#define __SELECTED_NODE_WND_H__ + +#include "monitor/MonitorMainWnd.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the selected node display window. +// --------------------------------------------------------------------------- + +class HOST_SW_API SelectedNodeWnd final : public finalcut::FDialog { +public: + /// + /// Initializes a new instance of the SelectedNodeWnd class. + /// + /// + explicit SelectedNodeWnd(FWidget* widget = nullptr) : FDialog{widget} + { + /* stub */ + } + /// Copy constructor. + SelectedNodeWnd(const SelectedNodeWnd&) = delete; + /// Move constructor. + SelectedNodeWnd(SelectedNodeWnd&&) noexcept = delete; + /// Finalizes an instance of the SelectedNodeWnd class. + ~SelectedNodeWnd() noexcept override = default; + + /// Disable copy assignment operator (=). + auto operator= (const SelectedNodeWnd&) -> SelectedNodeWnd& = delete; + /// Disable move assignment operator (=). + auto operator= (SelectedNodeWnd&&) noexcept -> SelectedNodeWnd& = 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 { } + + /// + void setSelectedText(std::string str) + { + m_selectedHost.setText(str); + redraw(); + } + +private: + FLabel m_selectedHostLabel{"Selected Host: ", this}; + FLabel m_selectedHost{this}; + + /// + /// + /// + void initLayout() override + { + 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, 2}, FSize{maxWidth, 2}); + FDialog::setMinimumSize(FSize{80, 5}); + FDialog::setResizeable(false); + FDialog::setMinimizable(false); + FDialog::setTitlebarButtonVisibility(false); + FDialog::setShadow(false); + + m_selectedHostLabel.setGeometry(FPoint(2, 1), FSize(18, 1)); + m_selectedHost.setGeometry(FPoint(20, 1), FSize(30, 1)); + m_selectedHost.setText("None"); + + FDialog::initLayout(); + } + + /// + /// + /// + void draw() override + { + setColor(); + clearArea(); + + const auto& wc = getColorTheme(); + setColor(wc->dialog_resize_fg, getBackgroundColor()); + + finalcut::drawBorder(this, FRect(FPoint{1, 1}, FPoint{(int)getWidth(), (int)getHeight()})); + } +}; + +#endif // __SELECTED_NODE_WND_H__ \ No newline at end of file diff --git a/src/monitor/TransmitWndBase.h b/src/monitor/TransmitWndBase.h new file mode 100644 index 00000000..9cd70c98 --- /dev/null +++ b/src/monitor/TransmitWndBase.h @@ -0,0 +1,285 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* 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(__TRANSMIT_WND_BASE_H__) +#define __TRANSMIT_WND_BASE_H__ + +#include "lookups/AffiliationLookup.h" +#include "network/RESTDefines.h" +#include "remote/RESTClient.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the base class for transmit windows. +// --------------------------------------------------------------------------- + +class HOST_SW_API TransmitWndBase : public finalcut::FDialog { +public: + /// + /// Initializes a new instance of the TransmitWndBase class. + /// + /// + /// + explicit TransmitWndBase(lookups::VoiceChData channel, FWidget* widget = nullptr) : FDialog{widget}, + m_selectedCh(channel) + { + /* stub */ + } + +protected: + bool m_hideModeSelect = false; + lookups::VoiceChData m_selectedCh; + + uint8_t m_mode = modem::STATE_DMR; + + /// + /// + /// + virtual void initLayout() override + { + FDialog::setMinimizable(true); + FDialog::setShadow(); + + std::size_t maxWidth, maxHeight; + const auto& rootWidget = getRootWidget(); + + if (rootWidget) { + maxWidth = rootWidget->getClientWidth(); + maxHeight = rootWidget->getClientHeight(); + } + else { + // fallback to xterm default size + maxWidth = 80; + maxHeight = 24; + } + + const int x = 1 + int((maxWidth - getWidth()) / 2); + const int y = 1 + int((maxHeight - getHeight()) / 3); + FWindow::setPos(FPoint{x, y}, false); + FDialog::adjustSize(); + + FDialog::setModal(); + + initControls(); + + FDialog::initLayout(); + + rootWidget->redraw(); // bryanb: wtf? + redraw(); + } + + /// + /// + /// + virtual void initControls() + { + resizeControls(); + + m_dmrSlotLabel.setGeometry(FPoint(2, 4), FSize(10, 1)); + m_dmrSlot.setGeometry(FPoint(18, 4), FSize(5, 1)); + m_dmrSlot.setRange(1, 2); + m_dmrSlot.setValue(1); + m_dmrSlot.setShadow(false); + + // callback REST API to get status of the channel + json::object req = json::object(); + json::object rsp = json::object(); + + int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.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", m_selectedCh.address().c_str(), m_selectedCh.port()); + } + + try { + if (rsp["fixedMode"].get()) { + m_mode = rsp["state"].is(); + m_hideModeSelect = true; + } + + bool dmrCC = rsp["dmrCC"].get(); + bool p25CC = rsp["p25CC"].get(); + bool nxdnCC = rsp["nxdnCC"].get(); + + // are we a dedicated control channel? + if (dmrCC || p25CC || nxdnCC) { + m_hideModeSelect = true; + if (dmrCC) { + m_mode = modem::STATE_DMR; + } + + if (p25CC) { + m_mode = modem::STATE_P25; + m_dmrSlot.setEnable(false); + redraw(); + } + + if (nxdnCC) { + m_mode = modem::STATE_NXDN; + m_dmrSlot.setEnable(false); + redraw(); + } + } + + // are we hiding the mode select? + if (!m_hideModeSelect) { + bool dmrEnabled = rsp["dmrEnabled"].get(); + bool p25Enabled = rsp["p25Enabled"].get(); + bool nxdnEnabled = rsp["nxdnEnabled"].get(); + + m_digModeGroup.setGeometry(FPoint(2, 1), FSize(56, 2)); + if (dmrEnabled) { + m_modeDMR.setPos(FPoint(1, 1)); + m_modeDMR.addCallback("toggled", [&]() { + if (m_modeDMR.isChecked()) { + m_mode = modem::STATE_DMR; + m_dmrSlot.setEnable(true); + redraw(); + } + }); + } + else { + m_modeDMR.setVisible(false); + } + + if (p25Enabled) { + m_modeP25.setPos(FPoint(13, 1)); + m_modeP25.addCallback("toggled", [&]() { + if (m_modeP25.isChecked()) { + m_mode = modem::STATE_P25; + m_dmrSlot.setEnable(false); + redraw(); + } + }); + } + else { + m_modeP25.setVisible(false); + } + + if (nxdnEnabled) { + m_modeNXDN.setPos(FPoint(22, 1)); + m_modeNXDN.addCallback("toggled", [&]() { + if (m_modeNXDN.isChecked()) { + m_mode = modem::STATE_NXDN; + m_dmrSlot.setEnable(false); + redraw(); + } + }); + } + else { + m_modeNXDN.setVisible(false); + } + } + else { + m_digModeGroup.setVisible(false); + m_modeDMR.setVisible(false); + m_modeP25.setVisible(false); + m_modeNXDN.setVisible(false); + m_dmrSlotLabel.setVisible(false); + m_dmrSlot.setVisible(false); + redraw(); + } + } + catch (std::exception&) { + /* stub */ + } + + focusFirstChild(); + } + + /// + /// + /// + void resizeControls() + { + // transmit button and close button logic + m_txButton.setGeometry(FPoint(3, int(getHeight()) - 6), FSize(10, 3)); + m_txButton.addCallback("clicked", [&]() { setTransmit(); }); + + m_closeButton.setGeometry(FPoint(17, int(getHeight()) - 6), FSize(9, 3)); + m_closeButton.addCallback("clicked", [&]() { hide(); }); + } + + /// + /// + /// + virtual void adjustSize() override + { + FDialog::adjustSize(); + } + + /* + ** Event Handlers + */ + + /// + /// + /// + /// + virtual void onKeyPress(finalcut::FKeyEvent* e) + { + const auto key = e->key(); + if (key == FKey::F12) { + setTransmit(); + } + } + + /// + /// + /// + /// + virtual void onClose(FCloseEvent* e) override + { + hide(); + } + +protected: + /// + /// + /// + virtual void setTransmit() + { + /* stub */ + } + +private: + FButton m_txButton{"Transmit", this}; + FButton m_closeButton{"Close", this}; + + FButtonGroup m_digModeGroup{"Digital Mode", this}; + FRadioButton m_modeDMR{"DMR", &m_digModeGroup}; + FRadioButton m_modeP25{"P25", &m_digModeGroup}; + FRadioButton m_modeNXDN{"NXDN", &m_digModeGroup}; + + FLabel m_dmrSlotLabel{"DMR Slot: ", this}; + +protected: + FSpinBox m_dmrSlot{this}; +}; + +#endif // __TRANSMIT_WND_BASE_H__ \ No newline at end of file diff --git a/src/monitor/UninhibitSubscriberWnd.h b/src/monitor/UninhibitSubscriberWnd.h new file mode 100644 index 00000000..0f0cdc64 --- /dev/null +++ b/src/monitor/UninhibitSubscriberWnd.h @@ -0,0 +1,144 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +/* +* 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(__UNINHIBIT_SUBSCRIBER_WND_H__) +#define __UNINHIBIT_SUBSCRIBER_WND_H__ + +#include "monitor/TransmitWndBase.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the uninhibit subscriber window. +// --------------------------------------------------------------------------- + +class HOST_SW_API UninhibitSubscriberWnd final : public TransmitWndBase { +public: + /// + /// Initializes a new instance of the UninhibitSubscriberWnd class. + /// + /// + /// + explicit UninhibitSubscriberWnd(lookups::VoiceChData channel, FWidget* widget = nullptr) : TransmitWndBase{channel, widget} + { + /* stub */ + } + +private: + FLabel m_dialogLabel{"Uninhibit Subscriber", this}; + + FLabel m_subscriberLabel{"Subscriber ID: ", this}; + FSpinBox m_subscriber{this}; + + /// + /// + /// + void initLayout() override + { + FDialog::setText("Uninhibit Subscriber"); + FDialog::setSize(FSize{60, 16}); + + TransmitWndBase::initLayout(); + } + + /// + /// + /// + void initControls() override + { + TransmitWndBase::initControls(); + + if (m_hideModeSelect) { + FDialog::setSize(FSize{60, 12}); + resizeControls(); + } + + // subscriber entry + { + if (!m_hideModeSelect) { + m_dialogLabel.setGeometry(FPoint(2, 6), FSize(20, 2)); + } + else { + m_dialogLabel.setGeometry(FPoint(2, 2), FSize(20, 2)); + } + m_dialogLabel.setEmphasis(); + m_dialogLabel.setAlignment(Align::Center); + + if (!m_hideModeSelect) { + m_subscriberLabel.setGeometry(FPoint(2, 8), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 8), FSize(20, 1)); + } + else { + m_subscriberLabel.setGeometry(FPoint(2, 4), FSize(25, 1)); + m_subscriber.setGeometry(FPoint(28, 4), FSize(20, 1)); + } + m_subscriber.setRange(1, 16777211); + m_subscriber.setValue(1); + m_subscriber.setShadow(false); + } + + m_subscriberLabel.redraw(); + redraw(); + } + + /// + /// + /// + void setTransmit() override + { + std::string method = PUT_DMR_RID; + json::object req = json::object(); + req["command"].set(RID_CMD_UNINHIBIT); + uint32_t dstId = m_subscriber.getValue(); + req["dstId"].set(dstId); + + switch (m_mode) { + case modem::STATE_DMR: + { + uint8_t slot = m_dmrSlot.getValue(); + req["slot"].set(slot); + } + break; + case modem::STATE_P25: + { + method = PUT_P25_RID; + } + break; + case modem::STATE_NXDN: + return; + } + + // callback REST API + int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), + HTTP_PUT, method, req, g_debug); + if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + ::LogError(LOG_HOST, "failed to send request to %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port()); + } + } +}; + +#endif // __UNINHIBIT_SUBSCRIBER_WND_H__ \ No newline at end of file diff --git a/src/network/RESTAPI.cpp b/src/network/RESTAPI.cpp index d6b35855..d509882f 100644 --- a/src/network/RESTAPI.cpp +++ b/src/network/RESTAPI.cpp @@ -508,6 +508,8 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, uint8_t protoVer = m_host->m_modem->getVersion(); response["protoVer"].set(protoVer); + response["fixedMode"].set(m_host->m_fixedMode); + response["dmrTSCCEnable"].set(m_host->m_dmrTSCCData); response["dmrCC"].set(m_host->m_dmrCtrlChannel); response["p25CtrlEnable"].set(m_host->m_p25CCData);