initial bare-bones implementation of dvmmon; enhance REST API to return more detailed status information (for use by dvmmon); cleanup file code style;

pull/33/head
Bryan Biedenkapp 3 years ago
parent 298cd8da1f
commit d45af90c07

@ -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"

@ -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:
/// <summary>
/// Initializes a new instance of the AdjustWndBase class.

@ -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:
/// <summary>
/// Initializes a new instance of the BERDisplayWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the ChannelConfigSetWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the CloseWndBase class.

@ -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:
/// <summary>
/// Initializes a new instance of the FIFOBufferAdjustWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the HSBandwidthAdjustWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the HSGainAdjustWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the LevelAdjustWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the LogDisplayWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the LoggingAndDataSetWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the ModemStatusWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the SiteParamSetWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the SymbLevelAdjustWnd class.

@ -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:
/// <summary>
/// Initializes a new instance of the SystemConfigSetWnd class.

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the inhibit subscriber window.
// ---------------------------------------------------------------------------
class HOST_SW_API InhibitSubscriberWnd final : public TransmitWndBase {
public:
/// <summary>
/// Initializes a new instance of the InhibitSubscriberWnd class.
/// </summary>
/// <param name="channel"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Inhibit Subscriber");
FDialog::setSize(FSize{60, 16});
TransmitWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
void setTransmit() override
{
std::string method = PUT_DMR_RID;
json::object req = json::object();
req["command"].set<std::string>(RID_CMD_INHIBIT);
uint32_t dstId = m_subscriber.getValue();
req["dstId"].set<uint32_t>(dstId);
switch (m_mode) {
case modem::STATE_DMR:
{
uint8_t slot = m_dmrSlot.getValue();
req["slot"].set<uint8_t>(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__

@ -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:
/// <summary>
/// Initializes a new instance of the LogDisplayWnd class.

@ -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 <configuration file>]\n\n"
" -c <file> specifies the configuration file to use\n"
::fprintf(stdout, "usage: %s [-dvh] [-c <monitor configuration file>]\n\n"
" -c <file> 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<std::string>();
uint32_t idenReloadTime = systemConf["iden_table"]["time"].as<uint32_t>(0U);
std::string idenLookupFile = g_conf["iden_table"]["file"].as<std::string>();
uint32_t idenReloadTime = g_conf["iden_table"]["time"].as<uint32_t>(0U);
if (idenLookupFile.length() <= 0U) {
::LogError(LOG_HOST, "No bandplan identity table? This must be defined!");
return 1;
}
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<uint32_t>(0U);
if (g_channelId > 15U) { // clamp to 15
g_channelId = 15U;
}
// show and start the application
wnd.show();

@ -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__

@ -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 <vector>
@ -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:
});
}
/// <summary></summary>
lookups::VoiceChData getSelectedCh() { return m_selectedCh; }
private:
friend class MonitorApplication;
LogDisplayWnd m_logWnd{this};
SelectedNodeWnd m_selectWnd{this};
std::vector<NodeStatusWnd*> m_nodes;
std::vector<uint32_t> m_voiceChNo;
std::unordered_map<uint32_t, lookups::VoiceChData> 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};
/// <summary>
///
@ -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<uint32_t>(0U);
if (channelId > 15U) { // clamp to 15
channelId = 15U;
}
// main/control channel
uint32_t mainChNo = 0U;
{
yaml::Node networkConf = g_conf["network"];
uint32_t chNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as<std::string>("1").c_str(), NULL, 16);
if (chNo == 0U) { // clamp to 1
chNo = 1U;
}
if (chNo > 4095U) { // clamp to 4095
chNo = 4095U;
}
mainChNo = chNo;
std::string restApiAddress = networkConf["restAddress"].as<std::string>("127.0.0.1");
if (restApiAddress == "0.0.0.0") {
restApiAddress = std::string("127.0.0.1");
}
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
// create configuration file node
NodeStatusWnd* wnd = new NodeStatusWnd(this);
wnd->setChData(data);
wnd->setChannelId(channelId);
wnd->setChannelNo(chNo);
wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
offsX += NODE_STATUS_WIDTH + 2;
m_nodes.push_back(wnd);
}
/*
** Voice Channels
** 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<std::string>("1").c_str(), NULL, 16);
if (chNo == 0U) { // clamp to 1
chNo = 1U;
}
if (chNo > 4095U) { // clamp to 4095
chNo = 4095U;
}
if (chNo == mainChNo) {
continue;
}
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Adddress %s:%u", g_channelId, chNo, restApiAddress.c_str(), restApiPort);
::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();
}

@ -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:
/// <summary>
/// Initializes a new instance of the NodeStatusWnd class.
@ -79,12 +78,14 @@ public:
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
/// <summary>Gets the channel ID.</summary>
uint8_t getChannelId() const { return m_channelId; }
/// <summary>Gets the channel number.</summary>
uint32_t getChannelNo() const { return m_channelNo; }
/// <summary>Gets the channel data.</summary>
lookups::VoiceChData getChData() { return m_chData; }
/// <summary>Sets the channel data.</summary>
void setChData(lookups::VoiceChData chData) { m_chData = chData; }
/// <summary>Sets the channel number.</summary>
void setChannelId(uint8_t channelId) { m_channelId = channelId; }
/// <summary>Sets the channel number.</summary>
void setChannelNo(uint32_t channelNo) { m_channelNo = channelNo; }
private:
int m_timerId;
@ -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:
}
}
/// <summary>
///
/// </summary>
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
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
void onWindowRaised(FEvent* e) override
{
FDialog::onWindowLowered(e);
emitCallback("update-selected");
}
/// <summary>
///
/// </summary>
@ -230,91 +259,83 @@ private:
setText("FAILED");
}
else {
// get remote node state
if (rsp["dmrTSCCEnable"].is<bool>() && rsp["p25CtrlEnable"].is<bool>() &&
rsp["nxdnCtrlEnable"].is<bool>()) {
bool dmrTSCCEnable = rsp["dmrTSCCEnable"].get<bool>();
bool dmrCC = rsp["dmrCC"].get<bool>();
bool p25CtrlEnable = rsp["p25CtrlEnable"].get<bool>();
bool p25CC = rsp["p25CC"].get<bool>();
bool p25VOC = rsp["p25VOC"].get<bool>();
bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = rsp["nxdnCC"].get<bool>();
// are we a dedicated control channel?
if (dmrCC || p25CC || nxdnCC) {
m_control = true;
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<bool>() && rsp["p25CtrlEnable"].is<bool>() &&
rsp["nxdnCtrlEnable"].is<bool>()) {
bool dmrTSCCEnable = rsp["dmrTSCCEnable"].get<bool>();
bool dmrCC = rsp["dmrCC"].get<bool>();
bool p25CtrlEnable = rsp["p25CtrlEnable"].get<bool>();
bool p25CC = rsp["p25CC"].get<bool>();
bool p25VOC = rsp["p25VOC"].get<bool>();
bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get<bool>();
bool nxdnCC = rsp["nxdnCC"].get<bool>();
// are we a dedicated control channel?
if (dmrCC || p25CC || nxdnCC) {
m_control = true;
if (p25CC && p25VOC) {
setText("CONTROL (VOC)");
}
else {
setText("CONTROL");
}
}
// are we transmitting?
if (rsp["tx"].is<bool>()) {
m_tx = rsp["tx"].get<bool>();
}
else {
::LogWarning(LOG_HOST, "%s:%u, does not report Tx status");
m_tx = false;
// if we aren't a dedicated control channel; set our
// title bar appropriately and set Tx state
if (!m_control) {
if (dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) {
setText("ENH. VOICE/CONV");
}
else {
setText("VOICE/CONV");
}
// are we transmitting?
if (rsp["tx"].is<bool>()) {
m_tx = rsp["tx"].get<bool>();
}
else {
::LogWarning(LOG_HOST, "%s:%u, does not report Tx status");
m_tx = false;
}
}
}
}
// get the remote node channel information
if (rsp["channelId"].is<uint8_t>() && rsp["channelNo"].is<uint32_t>()) {
uint8_t channelId = rsp["channelId"].get<uint8_t>();
uint32_t channelNo = rsp["channelNo"].get<uint32_t>();
bool resetControls = false;
// verify channel ID matches our configuration
if (channelId != m_channelId) {
::LogWarning(LOG_HOST, "%s:%u, reports chId = %u, disagrees with chId = %u", m_chData.address().c_str(), m_chData.port(), channelId, m_channelId);
m_channelId = channelId;
resetControls = true;
}
// get the remote node channel information
if (rsp["channelId"].is<uint8_t>() && rsp["channelNo"].is<uint32_t>()) {
uint8_t channelId = rsp["channelId"].get<uint8_t>();
uint32_t channelNo = rsp["channelNo"].get<uint32_t>();
// 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>()) {
uint32_t lastDstId = rsp["lastDstId"].get<uint32_t>();
if (lastDstId == 0) {
m_lastTG.setText("None");
// report last known transmitted destination ID
if (rsp["lastDstId"].is<uint32_t>()) {
uint32_t lastDstId = rsp["lastDstId"].get<uint32_t>();
if (lastDstId == 0) {
m_lastTG.setText("None");
}
else {
m_lastTG.setText(__INT_STR(lastDstId));
}
}
else {
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;
}
}
}

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the page subscriber window.
// ---------------------------------------------------------------------------
class HOST_SW_API PageSubscriberWnd final : public TransmitWndBase {
public:
/// <summary>
/// Initializes a new instance of the PageSubscriberWnd class.
/// </summary>
/// <param name="channel"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Page Subscriber");
FDialog::setSize(FSize{60, 16});
TransmitWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
void setTransmit() override
{
std::string method = PUT_DMR_RID;
json::object req = json::object();
req["command"].set<std::string>(RID_CMD_PAGE);
uint32_t dstId = m_subscriber.getValue();
req["dstId"].set<uint32_t>(dstId);
switch (m_mode) {
case modem::STATE_DMR:
{
uint8_t slot = m_dmrSlot.getValue();
req["slot"].set<uint8_t>(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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the radio check subscriber window.
// ---------------------------------------------------------------------------
class HOST_SW_API RadioCheckSubscriberWnd final : public TransmitWndBase {
public:
/// <summary>
/// Initializes a new instance of the RadioCheckSubscriberWnd class.
/// </summary>
/// <param name="channel"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Radio Check Subscriber");
FDialog::setSize(FSize{60, 16});
TransmitWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
void setTransmit() override
{
std::string method = PUT_DMR_RID;
json::object req = json::object();
req["command"].set<std::string>(RID_CMD_CHECK);
uint32_t dstId = m_subscriber.getValue();
req["dstId"].set<uint32_t>(dstId);
switch (m_mode) {
case modem::STATE_DMR:
{
uint8_t slot = m_dmrSlot.getValue();
req["slot"].set<uint8_t>(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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the selected node display window.
// ---------------------------------------------------------------------------
class HOST_SW_API SelectedNodeWnd final : public finalcut::FDialog {
public:
/// <summary>
/// Initializes a new instance of the SelectedNodeWnd class.
/// </summary>
/// <param name="widget"></param>
explicit SelectedNodeWnd(FWidget* widget = nullptr) : FDialog{widget}
{
/* stub */
}
/// <summary>Copy constructor.</summary>
SelectedNodeWnd(const SelectedNodeWnd&) = delete;
/// <summary>Move constructor.</summary>
SelectedNodeWnd(SelectedNodeWnd&&) noexcept = delete;
/// <summary>Finalizes an instance of the SelectedNodeWnd class.</summary>
~SelectedNodeWnd() noexcept override = default;
/// <summary>Disable copy assignment operator (=).</summary>
auto operator= (const SelectedNodeWnd&) -> SelectedNodeWnd& = delete;
/// <summary>Disable move assignment operator (=).</summary>
auto operator= (SelectedNodeWnd&&) noexcept -> SelectedNodeWnd& = delete;
/// <summary>Disable set X coordinate.</summary>
void setX(int, bool = true) override { }
/// <summary>Disable set Y coordinate.</summary>
void setY(int, bool = true) override { }
/// <summary>Disable set position.</summary>
void setPos(const FPoint&, bool = true) override { }
/// <summary></summary>
void setSelectedText(std::string str)
{
m_selectedHost.setText(str);
redraw();
}
private:
FLabel m_selectedHostLabel{"Selected Host: ", this};
FLabel m_selectedHost{this};
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the base class for transmit windows.
// ---------------------------------------------------------------------------
class HOST_SW_API TransmitWndBase : public finalcut::FDialog {
public:
/// <summary>
/// Initializes a new instance of the TransmitWndBase class.
/// </summary>
/// <param name="channel"></param>
/// <param name="widget"></param>
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;
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
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<bool>()) {
m_mode = rsp["state"].is<uint8_t>();
m_hideModeSelect = true;
}
bool dmrCC = rsp["dmrCC"].get<bool>();
bool p25CC = rsp["p25CC"].get<bool>();
bool nxdnCC = rsp["nxdnCC"].get<bool>();
// 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>();
bool p25Enabled = rsp["p25Enabled"].get<bool>();
bool nxdnEnabled = rsp["nxdnEnabled"].get<bool>();
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();
}
/// <summary>
///
/// </summary>
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(); });
}
/// <summary>
///
/// </summary>
virtual void adjustSize() override
{
FDialog::adjustSize();
}
/*
** Event Handlers
*/
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onKeyPress(finalcut::FKeyEvent* e)
{
const auto key = e->key();
if (key == FKey::F12) {
setTransmit();
}
}
/// <summary>
///
/// </summary>
/// <param name="e"></param>
virtual void onClose(FCloseEvent* e) override
{
hide();
}
protected:
/// <summary>
///
/// </summary>
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__

@ -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 <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the uninhibit subscriber window.
// ---------------------------------------------------------------------------
class HOST_SW_API UninhibitSubscriberWnd final : public TransmitWndBase {
public:
/// <summary>
/// Initializes a new instance of the UninhibitSubscriberWnd class.
/// </summary>
/// <param name="channel"></param>
/// <param name="widget"></param>
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};
/// <summary>
///
/// </summary>
void initLayout() override
{
FDialog::setText("Uninhibit Subscriber");
FDialog::setSize(FSize{60, 16});
TransmitWndBase::initLayout();
}
/// <summary>
///
/// </summary>
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();
}
/// <summary>
///
/// </summary>
void setTransmit() override
{
std::string method = PUT_DMR_RID;
json::object req = json::object();
req["command"].set<std::string>(RID_CMD_UNINHIBIT);
uint32_t dstId = m_subscriber.getValue();
req["dstId"].set<uint32_t>(dstId);
switch (m_mode) {
case modem::STATE_DMR:
{
uint8_t slot = m_dmrSlot.getValue();
req["slot"].set<uint8_t>(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__

@ -508,6 +508,8 @@ void RESTAPI::restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply,
uint8_t protoVer = m_host->m_modem->getVersion();
response["protoVer"].set<uint8_t>(protoVer);
response["fixedMode"].set<bool>(m_host->m_fixedMode);
response["dmrTSCCEnable"].set<bool>(m_host->m_dmrTSCCData);
response["dmrCC"].set<bool>(m_host->m_dmrCtrlChannel);
response["p25CtrlEnable"].set<bool>(m_host->m_p25CCData);

Loading…
Cancel
Save

Powered by TurnKey Linux.