initial implementation for a passive monitor tool; correct bad code style; implement feedback of last destination ID; enhance status REST API;
parent
ff97cc5ada
commit
8f7763cac4
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#if !defined(__LOG_DISPLAY_WND_H__)
|
||||
#define __LOG_DISPLAY_WND_H__
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// This class implements the log display window.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the LogDisplayWnd class.
|
||||
/// </summary>
|
||||
/// <param name="widget"></param>
|
||||
explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget}
|
||||
{
|
||||
m_scrollText.ignorePadding();
|
||||
|
||||
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
|
||||
}
|
||||
/// <summary>Copy constructor.</summary>
|
||||
LogDisplayWnd(const LogDisplayWnd&) = delete;
|
||||
/// <summary>Move constructor.</summary>
|
||||
LogDisplayWnd(LogDisplayWnd&&) noexcept = delete;
|
||||
/// <summary>Finalizes an instance of the LogDisplayWnd class.</summary>
|
||||
~LogDisplayWnd() noexcept override = default;
|
||||
|
||||
/// <summary>Disable copy assignment operator (=).</summary>
|
||||
auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete;
|
||||
/// <summary>Disable move assignment operator (=).</summary>
|
||||
auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete;
|
||||
|
||||
private:
|
||||
FTextView m_scrollText{this};
|
||||
int m_timerId;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void initLayout() override
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
auto lightning = "\u26a1";
|
||||
FDialog::setText("System Log"s + lightning);
|
||||
|
||||
const auto& rootWidget = getRootWidget();
|
||||
|
||||
FDialog::setGeometry(FPoint{(int)(rootWidget->getClientWidth() - 81), (int)(rootWidget->getClientHeight() - 20)}, FSize{80, 20});
|
||||
FDialog::setMinimumSize(FSize{80, 20});
|
||||
FDialog::setResizeable(true);
|
||||
FDialog::setMinimizable(true);
|
||||
FDialog::setTitlebarButtonVisibility(true);
|
||||
FDialog::setShadow();
|
||||
|
||||
minimizeWindow();
|
||||
|
||||
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
|
||||
|
||||
FDialog::initLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void adjustSize() override
|
||||
{
|
||||
FDialog::adjustSize();
|
||||
|
||||
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
void onClose(FCloseEvent* e) override
|
||||
{
|
||||
minimizeWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="timer"></param>
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
if (timer->getTimerId() == m_timerId) {
|
||||
if (str().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scrollText.append(str());
|
||||
str("");
|
||||
m_scrollText.scrollToEnd();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __LOG_DISPLAY_WND_H__
|
||||
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#if !defined(__MONITOR_APPLICATION_H__)
|
||||
#define __MONITOR_APPLICATION_H__
|
||||
|
||||
#include "monitor/MonitorMain.h"
|
||||
#include "monitor/MonitorMainWnd.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// This class implements the finalcut application.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API MonitorApplication final : public finalcut::FApplication {
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MonitorApplication class.
|
||||
/// </summary>
|
||||
/// <param name="argc"></param>
|
||||
/// <param name="argv"></param>
|
||||
explicit MonitorApplication(const int& argc, char** argv) : FApplication{argc, argv}
|
||||
{
|
||||
m_statusRefreshTimer = addTimer(1000);
|
||||
}
|
||||
|
||||
protected:
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
virtual void processExternalUserEvent()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="timer"></param>
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
if (timer->getTimerId() == m_statusRefreshTimer) {
|
||||
/* stub */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int m_statusRefreshTimer;
|
||||
};
|
||||
|
||||
#endif // __MONITOR_APPLICATION_H__
|
||||
@ -0,0 +1,258 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp <gatekeep@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "Log.h"
|
||||
#include "monitor/MonitorMain.h"
|
||||
#include "monitor/MonitorApplication.h"
|
||||
#include "monitor/MonitorMainWnd.h"
|
||||
#include "yaml/Yaml.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace lookups;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define IS(s) (::strcmp(argv[i], s) == 0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Variables
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string g_progExe = std::string(__EXE_NAME__);
|
||||
std::string g_iniFile = std::string(DEFAULT_CONF_FILE);
|
||||
yaml::Node g_conf;
|
||||
bool g_debug = false;
|
||||
|
||||
lookups::IdenTableLookup* g_idenTable = nullptr;
|
||||
uint32_t g_channelId = 0U;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Helper to print a fatal error message and exit.
|
||||
/// </summary>
|
||||
/// <remarks>This is a variable argument function.</remarks>
|
||||
/// <param name="msg">Message.</param>
|
||||
void fatal(const char* msg, ...)
|
||||
{
|
||||
char buffer[400U];
|
||||
::memset(buffer, 0x20U, 400U);
|
||||
|
||||
va_list vl;
|
||||
va_start(vl, msg);
|
||||
|
||||
::vsprintf(buffer, msg, vl);
|
||||
|
||||
va_end(vl);
|
||||
|
||||
::fprintf(stderr, "%s: %s\n", g_progExe.c_str(), buffer);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to pring usage the command line arguments. (And optionally an error.)
|
||||
/// </summary>
|
||||
/// <param name="message">Error message.</param>
|
||||
/// <param name="arg">Error message arguments.</param>
|
||||
void usage(const char* message, const char* arg)
|
||||
{
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
|
||||
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
|
||||
if (message != nullptr) {
|
||||
::fprintf(stderr, "%s: ", g_progExe.c_str());
|
||||
::fprintf(stderr, message, arg);
|
||||
::fprintf(stderr, "\n\n");
|
||||
}
|
||||
|
||||
::fprintf(stdout, "usage: %s [-dvh] [-c <configuration file>]\n\n"
|
||||
" -c <file> specifies the configuration file to use\n"
|
||||
"\n"
|
||||
" -d enable debug\n"
|
||||
" -v show version information\n"
|
||||
" -h show this screen\n"
|
||||
" -- stop handling options\n",
|
||||
g_progExe.c_str());
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to validate the command line arguments.
|
||||
/// </summary>
|
||||
/// <param name="argc">Argument count.</param>
|
||||
/// <param name="argv">Array of argument strings.</param>
|
||||
/// <returns>Count of remaining unprocessed arguments.</returns>
|
||||
int checkArgs(int argc, char* argv[])
|
||||
{
|
||||
int i, p = 0;
|
||||
|
||||
// iterate through arguments
|
||||
for (i = 1; i <= argc; i++)
|
||||
{
|
||||
if (argv[i] == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*argv[i] != '-') {
|
||||
continue;
|
||||
}
|
||||
else if (IS("--")) {
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
else if (IS("-c")) {
|
||||
if (argc-- <= 0)
|
||||
usage("error: %s", "must specify the configuration file to use");
|
||||
g_iniFile = std::string(argv[++i]);
|
||||
|
||||
if (g_iniFile == "")
|
||||
usage("error: %s", "configuration file cannot be blank!");
|
||||
|
||||
p += 2;
|
||||
}
|
||||
else if (IS("-d")) {
|
||||
++p;
|
||||
g_debug = true;
|
||||
}
|
||||
else if (IS("-v")) {
|
||||
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
|
||||
::fprintf(stdout, "Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n");
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else if (IS("-h")) {
|
||||
usage(nullptr, nullptr);
|
||||
if (argc == 2)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
else {
|
||||
usage("unrecognized option `%s'", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0 || p > argc) {
|
||||
p = 0;
|
||||
}
|
||||
|
||||
return ++p;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Program Entry Point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argv[0] != nullptr && *argv[0] != 0)
|
||||
g_progExe = std::string(argv[0]);
|
||||
|
||||
if (argc > 1) {
|
||||
// check arguments
|
||||
int i = checkArgs(argc, argv);
|
||||
if (i < argc) {
|
||||
argc -= i;
|
||||
argv += i;
|
||||
}
|
||||
else {
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize system logging
|
||||
bool ret = ::LogInitialise("", "", 0U, 1U);
|
||||
if (!ret) {
|
||||
::fprintf(stderr, "unable to open the log file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
getHostVersion();
|
||||
::LogInfo(">> Monitor");
|
||||
|
||||
try {
|
||||
ret = yaml::Parse(g_conf, g_iniFile.c_str());
|
||||
if (!ret) {
|
||||
::fatal("cannot read the configuration file, %s\n", g_iniFile.c_str());
|
||||
}
|
||||
}
|
||||
catch (yaml::OperationException const& e) {
|
||||
::fatal("cannot read the configuration file - %s (%s)", g_iniFile.c_str(), e.message());
|
||||
}
|
||||
|
||||
// setup the finalcut tui
|
||||
MonitorApplication app{argc, argv};
|
||||
|
||||
MonitorMainWnd wnd{&app};
|
||||
finalcut::FWidget::setMainWidget(&wnd);
|
||||
|
||||
yaml::Node systemConf = g_conf["system"];
|
||||
|
||||
// try to load bandplan identity table
|
||||
std::string idenLookupFile = systemConf["iden_table"]["file"].as<std::string>();
|
||||
uint32_t idenReloadTime = systemConf["iden_table"]["time"].as<uint32_t>(0U);
|
||||
|
||||
if (idenLookupFile.length() <= 0U) {
|
||||
::LogError(LOG_HOST, "No bandplan identity table? This must be defined!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_logDisplayLevel = 0U;
|
||||
|
||||
LogInfo("Iden Table Lookups");
|
||||
LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None");
|
||||
if (idenReloadTime > 0U)
|
||||
LogInfo(" Reload: %u mins", idenReloadTime);
|
||||
|
||||
g_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime);
|
||||
g_idenTable->read();
|
||||
|
||||
yaml::Node rfssConfig = systemConf["config"];
|
||||
g_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
|
||||
if (g_channelId > 15U) { // clamp to 15
|
||||
g_channelId = 15U;
|
||||
}
|
||||
|
||||
// show and start the application
|
||||
wnd.show();
|
||||
|
||||
finalcut::FApplication::setDarkTheme();
|
||||
app.resetColors();
|
||||
app.redraw();
|
||||
|
||||
int _errno = app.exec();
|
||||
::LogFinalise();
|
||||
return _errno;
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#if !defined(__MONITOR_MAIN_H__)
|
||||
#define __MONITOR_MAIN_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "lookups/IdenTableLookup.h"
|
||||
#include "yaml/Yaml.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#undef __PROG_NAME__
|
||||
#define __PROG_NAME__ "Digital Voice Modem (DVM) Monitor Tool"
|
||||
#undef __EXE_NAME__
|
||||
#define __EXE_NAME__ "dvmmon"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Externs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
extern std::string g_progExe;
|
||||
extern std::string g_iniFile;
|
||||
extern yaml::Node g_conf;
|
||||
extern bool g_debug;
|
||||
|
||||
extern lookups::IdenTableLookup* g_idenTable;
|
||||
extern uint32_t g_channelId;
|
||||
|
||||
#endif // __MONITOR_MAIN_H__
|
||||
@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#if !defined(__MONITOR_WND_H__)
|
||||
#define __MONITOR_WND_H__
|
||||
|
||||
#include "lookups/AffiliationLookup.h"
|
||||
#include "Log.h"
|
||||
#include "Thread.h"
|
||||
|
||||
using namespace lookups;
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
#undef null
|
||||
|
||||
#include "monitor/MonitorMain.h"
|
||||
|
||||
#include "monitor/LogDisplayWnd.h"
|
||||
#include "monitor/NodeStatusWnd.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Prototypes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API MonitorApplication;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// This class implements the root window control.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API MonitorMainWnd final : public finalcut::FWidget {
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MonitorMainWnd class.
|
||||
/// </summary>
|
||||
/// <param name="widget"></param>
|
||||
explicit MonitorMainWnd(FWidget* widget = nullptr) : FWidget{widget}
|
||||
{
|
||||
__InternalOutputStream(m_logWnd);
|
||||
|
||||
// file menu
|
||||
m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X
|
||||
m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this);
|
||||
m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this);
|
||||
|
||||
// command menu
|
||||
m_cmdMenuSeparator1.setSeparator();
|
||||
m_cmdMenuSeparator2.setSeparator();
|
||||
|
||||
// engineering menu
|
||||
m_engineeringMenuSeparator1.setSeparator();
|
||||
m_engineeringMenuSeparator2.setSeparator();
|
||||
|
||||
// help menu
|
||||
m_aboutItem.addCallback("clicked", this, [&]() {
|
||||
const FString line(2, UniChar::BoxDrawingsHorizontal);
|
||||
FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n"
|
||||
L"Version " + __VER__ + L"\n\n"
|
||||
L"Copyright (c) 2017-2023 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n"
|
||||
L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others",
|
||||
FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this);
|
||||
info.setCenterText();
|
||||
info.show();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
friend class MonitorApplication;
|
||||
|
||||
LogDisplayWnd m_logWnd{this};
|
||||
std::vector<NodeStatusWnd*> m_nodes;
|
||||
|
||||
std::vector<uint32_t> m_voiceChNo;
|
||||
std::unordered_map<uint32_t, lookups::VoiceChData> m_voiceChData;
|
||||
|
||||
FString m_line{13, UniChar::BoxDrawingsHorizontal};
|
||||
|
||||
FMenuBar m_menuBar{this};
|
||||
|
||||
FMenu m_fileMenu{"&File", &m_menuBar};
|
||||
FMenuItem m_quitItem{"&Quit", &m_fileMenu};
|
||||
|
||||
FMenu m_cmdMenu{"&Commands", &m_menuBar};
|
||||
FMenuItem m_pageSU{"&Page Subscriber", &m_cmdMenu};
|
||||
FMenuItem m_radioCheckSU{"Radio &Check Subscriber", &m_cmdMenu};
|
||||
FMenuItem m_cmdMenuSeparator1{&m_cmdMenu};
|
||||
FMenuItem m_inhibitSU{"&Inhibit Subscriber", &m_cmdMenu};
|
||||
FMenuItem m_uninhibitSU{"&Uninhibit Subscriber", &m_cmdMenu};
|
||||
FMenuItem m_cmdMenuSeparator2{&m_cmdMenu};
|
||||
FMenuItem m_gaqSU{"&Group Affiliation Query", &m_cmdMenu};
|
||||
FMenuItem m_uregSU{"&Force Unit Registration", &m_cmdMenu};
|
||||
|
||||
FMenu m_engineeringMenu{"&Engineering", &m_menuBar};
|
||||
FMenuItem m_toggleDMRDebug{"Toggle DMR Debug", &m_engineeringMenu};
|
||||
FMenuItem m_toggleDMRCSBKDump{"Toggle DMR CSBK Dump", &m_engineeringMenu};
|
||||
FMenuItem m_engineeringMenuSeparator1{&m_engineeringMenu};
|
||||
FMenuItem m_toggleP25Debug{"Toggle P25 Debug", &m_engineeringMenu};
|
||||
FMenuItem m_toggleP25TSBKDump{"Toggle P25 TSBK Dump", &m_engineeringMenu};
|
||||
FMenuItem m_engineeringMenuSeparator2{&m_engineeringMenu};
|
||||
FMenuItem m_toggleNXDNDebug{"Toggle NXDN Debug", &m_engineeringMenu};
|
||||
FMenuItem m_toggleNXDNRCCHDump{"Toggle NXDN RCCH Dump", &m_engineeringMenu};
|
||||
|
||||
FMenu m_helpMenu{"&Help", &m_menuBar};
|
||||
FMenuItem m_aboutItem{"&About", &m_helpMenu};
|
||||
|
||||
FStatusBar m_statusBar{this};
|
||||
FStatusKey m_keyF3{FKey::F3, "Quit", &m_statusBar};
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void intializeNodeDisplay()
|
||||
{
|
||||
const auto& rootWidget = getRootWidget();
|
||||
const int defaultOffsX = 2;
|
||||
int offsX = defaultOffsX, offsY = 2;
|
||||
|
||||
int maxWidth = 77;
|
||||
if (rootWidget) {
|
||||
maxWidth = rootWidget->getClientWidth() - 3;
|
||||
}
|
||||
|
||||
yaml::Node systemConf = g_conf["system"];
|
||||
yaml::Node rfssConfig = systemConf["config"];
|
||||
uint8_t channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
|
||||
if (channelId > 15U) { // clamp to 15
|
||||
channelId = 15U;
|
||||
}
|
||||
|
||||
// main/control channel
|
||||
uint32_t mainChNo = 0U;
|
||||
{
|
||||
yaml::Node networkConf = g_conf["network"];
|
||||
uint32_t chNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as<std::string>("1").c_str(), NULL, 16);
|
||||
if (chNo == 0U) { // clamp to 1
|
||||
chNo = 1U;
|
||||
}
|
||||
if (chNo > 4095U) { // clamp to 4095
|
||||
chNo = 4095U;
|
||||
}
|
||||
|
||||
mainChNo = chNo;
|
||||
|
||||
//std::string restApiAddress = networkConf["restAddress"].as<std::string>("127.0.0.1");
|
||||
std::string restApiAddress = std::string("127.0.0.1");
|
||||
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
|
||||
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
|
||||
|
||||
VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
|
||||
|
||||
// create configuration file node
|
||||
NodeStatusWnd* wnd = new NodeStatusWnd(this);
|
||||
wnd->setChData(data);
|
||||
wnd->setChannelId(channelId);
|
||||
wnd->setChannelNo(chNo);
|
||||
|
||||
wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
|
||||
offsX += NODE_STATUS_WIDTH + 2;
|
||||
m_nodes.push_back(wnd);
|
||||
}
|
||||
|
||||
/*
|
||||
** Voice Channels
|
||||
*/
|
||||
yaml::Node& voiceChList = rfssConfig["voiceChNo"];
|
||||
|
||||
if (voiceChList.size() != 0U) {
|
||||
for (size_t i = 0; i < voiceChList.size(); i++) {
|
||||
yaml::Node& channel = voiceChList[i];
|
||||
|
||||
uint32_t chNo = (uint32_t)::strtoul(channel["channelNo"].as<std::string>("1").c_str(), NULL, 16);
|
||||
if (chNo == 0U) { // clamp to 1
|
||||
chNo = 1U;
|
||||
}
|
||||
if (chNo > 4095U) { // clamp to 4095
|
||||
chNo = 4095U;
|
||||
}
|
||||
|
||||
if (chNo == mainChNo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
|
||||
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
|
||||
std::string restApiPassword = channel["restPassword"].as<std::string>();
|
||||
|
||||
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Adddress %s:%u", g_channelId, chNo, restApiAddress.c_str(), restApiPort);
|
||||
|
||||
VoiceChData data = VoiceChData(chNo, restApiAddress, restApiPort, restApiPassword);
|
||||
|
||||
NodeStatusWnd* wnd = new NodeStatusWnd(this);
|
||||
wnd->setChData(data);
|
||||
wnd->setChannelId(channelId);
|
||||
wnd->setChannelNo(chNo);
|
||||
|
||||
// set control position
|
||||
if (offsX + NODE_STATUS_WIDTH > maxWidth) {
|
||||
offsY += NODE_STATUS_HEIGHT + 2;
|
||||
offsX = defaultOffsX;
|
||||
}
|
||||
|
||||
wnd->setGeometry(FPoint{offsX, offsY}, FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
|
||||
offsX += NODE_STATUS_WIDTH + 2;
|
||||
m_nodes.push_back(wnd);
|
||||
}
|
||||
}
|
||||
|
||||
// display all the node windows
|
||||
for (auto* wnd : m_nodes) {
|
||||
wnd->setModal(false);
|
||||
wnd->show();
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
/*
|
||||
** Event Handlers
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
void onShow(FShowEvent* e) override
|
||||
{
|
||||
intializeNodeDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
void onClose(FCloseEvent* e) override
|
||||
{
|
||||
FApplication::closeConfirmationDialog(this, e);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __MONITOR_WND_H__
|
||||
@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Digital Voice Modem - Monitor
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Monitor
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#if !defined(__NODE_STATUS_WND_H__)
|
||||
#define __NODE_STATUS_WND_H__
|
||||
|
||||
#include "lookups/AffiliationLookup.h"
|
||||
#include "modem/Modem.h"
|
||||
#include "network/RESTDefines.h"
|
||||
#include "remote/RESTClient.h"
|
||||
|
||||
#include "monitor/MonitorMainWnd.h"
|
||||
|
||||
#include <final/final.h>
|
||||
using namespace finalcut;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define NODE_STATUS_WIDTH 28
|
||||
#define NODE_STATUS_HEIGHT 7
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// This class implements the node status display window.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the NodeStatusWnd class.
|
||||
/// </summary>
|
||||
/// <param name="widget"></param>
|
||||
explicit NodeStatusWnd(FWidget* widget = nullptr) : FDialog{widget}
|
||||
{
|
||||
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
|
||||
m_reconnectTimerId = addTimer(15000); // starts the timer every 10 seconds
|
||||
}
|
||||
/// <summary>Copy constructor.</summary>
|
||||
NodeStatusWnd(const NodeStatusWnd&) = delete;
|
||||
/// <summary>Move constructor.</summary>
|
||||
NodeStatusWnd(NodeStatusWnd&&) noexcept = delete;
|
||||
/// <summary>Finalizes an instance of the NodeStatusWnd class.</summary>
|
||||
~NodeStatusWnd() noexcept override = default;
|
||||
|
||||
/// <summary>Disable copy assignment operator (=).</summary>
|
||||
auto operator= (const NodeStatusWnd&) -> NodeStatusWnd& = delete;
|
||||
/// <summary>Disable move assignment operator (=).</summary>
|
||||
auto operator= (NodeStatusWnd&&) noexcept -> NodeStatusWnd& = delete;
|
||||
|
||||
/// <summary>Disable set X coordinate.</summary>
|
||||
void setX(int, bool = true) override { }
|
||||
/// <summary>Disable set Y coordinate.</summary>
|
||||
void setY(int, bool = true) override { }
|
||||
/// <summary>Disable set position.</summary>
|
||||
void setPos(const FPoint&, bool = true) override { }
|
||||
|
||||
/// <summary>Sets the channel data.</summary>
|
||||
void setChData(lookups::VoiceChData chData) { m_chData = chData; }
|
||||
/// <summary>Sets the channel number.</summary>
|
||||
void setChannelId(uint8_t channelId) { m_channelId = channelId; }
|
||||
/// <summary>Sets the channel number.</summary>
|
||||
void setChannelNo(uint32_t channelNo) { m_channelNo = channelNo; }
|
||||
|
||||
private:
|
||||
int m_timerId;
|
||||
int m_reconnectTimerId;
|
||||
|
||||
bool m_failed;
|
||||
bool m_control;
|
||||
bool m_tx;
|
||||
|
||||
lookups::VoiceChData m_chData;
|
||||
uint8_t m_channelId;
|
||||
uint32_t m_channelNo;
|
||||
|
||||
FLabel m_channelNoLabel{"Ch. No.: ", this};
|
||||
FLabel m_chanNo{this};
|
||||
|
||||
FLabel m_txFreqLabel{"Tx: ", this};
|
||||
FLabel m_txFreq{this};
|
||||
FLabel m_rxFreqLabel{"Rx: ", this};
|
||||
FLabel m_rxFreq{this};
|
||||
|
||||
FLabel m_lastTGLabel{"Last TG: ", this};
|
||||
FLabel m_lastTG{this};
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void initLayout() override
|
||||
{
|
||||
FDialog::setMinimumSize(FSize{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT});
|
||||
|
||||
FDialog::setResizeable(false);
|
||||
FDialog::setMinimizable(false);
|
||||
FDialog::setTitlebarButtonVisibility(false);
|
||||
FDialog::setShadow(false);
|
||||
FDialog::setModal(false);
|
||||
|
||||
FDialog::setText("UNKNOWN");
|
||||
|
||||
initControls();
|
||||
|
||||
FDialog::initLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void draw() override
|
||||
{
|
||||
FDialog::draw();
|
||||
|
||||
if (m_failed) {
|
||||
setColor(FColor::Yellow1, FColor::Red3);
|
||||
}
|
||||
else if (m_control) {
|
||||
setColor(FColor::LightGray, FColor::Purple3);
|
||||
}
|
||||
else if (m_tx) {
|
||||
setColor(FColor::LightGray, FColor::Green3);
|
||||
}
|
||||
else {
|
||||
setColor(FColor::LightGray, FColor::Black);
|
||||
}
|
||||
|
||||
finalcut::drawBorder(this, FRect(FPoint{1, 2}, FPoint{NODE_STATUS_WIDTH, NODE_STATUS_HEIGHT}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void initControls()
|
||||
{
|
||||
// channel number
|
||||
{
|
||||
m_channelNoLabel.setGeometry(FPoint(2, 1), FSize(10, 1));
|
||||
|
||||
m_chanNo.setGeometry(FPoint(11, 1), FSize(8, 1));
|
||||
m_chanNo.setText(__INT_STR(m_channelNo));
|
||||
}
|
||||
|
||||
// channel frequency
|
||||
{
|
||||
IdenTable entry = g_idenTable->find(m_channelId);
|
||||
if (entry.baseFrequency() == 0U) {
|
||||
::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId);
|
||||
}
|
||||
|
||||
if (entry.txOffsetMhz() == 0U) {
|
||||
::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId);
|
||||
}
|
||||
|
||||
uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125);
|
||||
float calcTxOffset = entry.txOffsetMhz() * 1000000;
|
||||
|
||||
uint32_t rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset);
|
||||
uint32_t txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)));
|
||||
|
||||
m_txFreqLabel.setGeometry(FPoint(2, 2), FSize(4, 1));
|
||||
m_txFreq.setGeometry(FPoint(6, 2), FSize(8, 1));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::fixed << std::setprecision(4) << (float)(txFrequency / 1000000.0f);
|
||||
|
||||
m_txFreq.setText(ss.str());
|
||||
|
||||
m_rxFreqLabel.setGeometry(FPoint(2, 3), FSize(4, 1));
|
||||
m_rxFreq.setGeometry(FPoint(6, 3), FSize(8, 1));
|
||||
|
||||
ss.str(std::string());
|
||||
ss << std::fixed << std::setprecision(4) << (float)(rxFrequency / 1000000.0f);
|
||||
|
||||
m_rxFreq.setText(ss.str());
|
||||
}
|
||||
|
||||
// last TG
|
||||
{
|
||||
m_lastTGLabel.setGeometry(FPoint(2, 4), FSize(10, 1));
|
||||
|
||||
m_lastTG.setGeometry(FPoint(11, 4), FSize(8, 1));
|
||||
m_lastTG.setText("None");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="timer"></param>
|
||||
void onTimer(FTimerEvent* timer) override
|
||||
{
|
||||
if (timer != nullptr) {
|
||||
// update timer
|
||||
if (timer->getTimerId() == m_timerId) {
|
||||
if (!m_failed) {
|
||||
// callback REST API to get status of the channel we represent
|
||||
json::object req = json::object();
|
||||
json::object rsp = json::object();
|
||||
|
||||
int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(),
|
||||
HTTP_GET, GET_STATUS, req, rsp, g_debug);
|
||||
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
|
||||
::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
|
||||
m_failed = true;
|
||||
setText("FAILED");
|
||||
}
|
||||
else {
|
||||
// get remote node state
|
||||
if (rsp["dmrTSCCEnable"].is<bool>() && rsp["p25CtrlEnable"].is<bool>() &&
|
||||
rsp["nxdnCtrlEnable"].is<bool>()) {
|
||||
bool dmrTSCCEnable = rsp["dmrTSCCEnable"].get<bool>();
|
||||
bool dmrCC = rsp["dmrCC"].get<bool>();
|
||||
bool p25CtrlEnable = rsp["p25CtrlEnable"].get<bool>();
|
||||
bool p25CC = rsp["p25CC"].get<bool>();
|
||||
bool nxdnCtrlEnable = rsp["nxdnCtrlEnable"].get<bool>();
|
||||
bool nxdnCC = rsp["nxdnCC"].get<bool>();
|
||||
|
||||
// are we a dedicated control channel?
|
||||
if (dmrCC || p25CC || nxdnCC) {
|
||||
m_control = true;
|
||||
setText("CONTROL");
|
||||
}
|
||||
|
||||
// if we aren't a dedicated control channel; set our
|
||||
// title bar appropriately and set Tx state
|
||||
if (!m_control) {
|
||||
if (dmrTSCCEnable || p25CtrlEnable || nxdnCtrlEnable) {
|
||||
setText("ENH. VOICE/CONV");
|
||||
}
|
||||
else {
|
||||
setText("VOICE/CONV");
|
||||
}
|
||||
|
||||
// are we transmitting?
|
||||
if (rsp["tx"].is<bool>()) {
|
||||
m_tx = rsp["tx"].get<bool>();
|
||||
}
|
||||
else {
|
||||
::LogWarning(LOG_HOST, "%s:%u, does not report Tx status");
|
||||
m_tx = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the remote node channel information
|
||||
if (rsp["channelId"].is<uint8_t>() && rsp["channelNo"].is<uint32_t>()) {
|
||||
uint8_t channelId = rsp["channelId"].get<uint8_t>();
|
||||
uint32_t channelNo = rsp["channelNo"].get<uint32_t>();
|
||||
bool resetControls = false;
|
||||
|
||||
// verify channel ID matches our configuration
|
||||
if (channelId != m_channelId) {
|
||||
::LogWarning(LOG_HOST, "%s:%u, reports chId = %u, disagrees with chId = %u", m_chData.address().c_str(), m_chData.port(), channelId, m_channelId);
|
||||
m_channelId = channelId;
|
||||
resetControls = true;
|
||||
}
|
||||
|
||||
// verify channel number matches our configuration
|
||||
if (channelNo != m_channelNo) {
|
||||
::LogWarning(LOG_HOST, "%s:%u, reports chNo = %u, disagrees with chNo = %u", m_chData.address().c_str(), m_chData.port(), channelNo, m_channelNo);
|
||||
m_channelNo = channelNo;
|
||||
resetControls = true;
|
||||
}
|
||||
|
||||
// reset controls for our display if necessary
|
||||
if (resetControls) {
|
||||
initControls();
|
||||
setText(getText() + "*");
|
||||
}
|
||||
}
|
||||
else {
|
||||
::LogWarning(LOG_HOST, "%s:%u, does not report channel information");
|
||||
}
|
||||
|
||||
// report last known transmitted destination ID
|
||||
if (rsp["lastDstId"].is<uint32_t>()) {
|
||||
uint32_t lastDstId = rsp["lastDstId"].get<uint32_t>();
|
||||
if (lastDstId == 0) {
|
||||
m_lastTG.setText("None");
|
||||
}
|
||||
else {
|
||||
m_lastTG.setText(__INT_STR(lastDstId));
|
||||
}
|
||||
}
|
||||
else {
|
||||
::LogWarning(LOG_HOST, "%s:%u, does not report last TG information");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
// reconnect timer
|
||||
if (timer->getTimerId() == m_reconnectTimerId) {
|
||||
if (m_failed) {
|
||||
::LogInfoEx(LOG_HOST, "attempting to reconnect to %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
|
||||
// callback REST API to get status of the channel we represent
|
||||
json::object req = json::object();
|
||||
int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(),
|
||||
HTTP_GET, GET_STATUS, req, g_debug);
|
||||
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
|
||||
::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo);
|
||||
}
|
||||
else {
|
||||
m_failed = false;
|
||||
setText("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __NODE_STATUS_WND_H__
|
||||
Loading…
Reference in new issue