// SPDX-License-Identifier: GPL-2.0-only /* * Digital Voice Modem - Modem Host Software * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2017-2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2021 Nat Moore * */ #include "Defines.h" #include "common/lookups/RSSIInterpolator.h" #include "common/network/udp/Socket.h" #include "common/Log.h" #include "common/StopWatch.h" #include "common/Thread.h" #include "common/Utils.h" #include "modem/port/specialized/V24UDPPort.h" #include "remote/RESTClient.h" #include "host/Host.h" #include "ActivityLog.h" #include "HostMain.h" using namespace modem; using namespace lookups; #include #include #include #include #if !defined(_WIN32) #include #include #endif // !defined(_WIN32) // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- std::mutex Host::m_clockingMutex; uint8_t Host::m_activeTickDelay = 5U; uint8_t Host::m_idleTickDelay = 5U; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- #define CW_IDLE_SLEEP_MS 50U #define IDLE_WARMUP_MS 5U #define MAX_OVERFLOW_CNT 10U // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /* Initializes a new instance of the Host class. */ Host::Host(const std::string& confFile) : m_confFile(confFile), m_conf(), m_modem(nullptr), m_modemRemote(false), m_isModemDFSI(false), m_udpDSFIRemotePort(nullptr), m_network(nullptr), m_modemRemotePort(nullptr), m_state(STATE_IDLE), m_isTxCW(false), m_modeTimer(1000U), m_dmrTXTimer(1000U), m_cwIdTimer(1000U), m_dmrEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), m_duplex(false), m_fixedMode(false), m_timeout(180U), m_rfModeHang(10U), m_rfTalkgroupHang(10U), m_netModeHang(3U), m_lastDstId(0U), m_lastSrcId(0U), m_allowStatusTransfer(true), m_identity(), m_cwCallsign(), m_cwIdTime(0U), m_latitude(0.0F), m_longitude(0.0F), m_height(0), m_power(0U), m_location(), m_rxFrequency(0U), m_txFrequency(0U), m_channelId(0U), m_channelNo(0U), m_channelLookup(), m_voiceChPeerId(), m_controlChData(), m_idenTable(nullptr), m_ridLookup(nullptr), m_tidLookup(nullptr), m_dmrBeacons(false), m_dmrTSCCData(false), m_dmrCtrlChannel(false), m_p25CCData(false), m_p25CtrlChannel(false), m_p25CtrlBroadcast(false), m_nxdnCCData(false), m_nxdnCtrlChannel(false), m_nxdnCtrlBroadcast(false), m_presenceTime(120U), m_siteId(1U), m_sysId(1U), m_dmrNetId(1U), m_dmrColorCode(1U), m_p25NAC(0x293U), m_p25NetId(0xBB800U), m_p25RfssId(1U), m_nxdnRAN(1U), m_dmrQueueSizeBytes(3960U), // 24 frames m_p25QueueSizeBytes(2592U), // 12 frames m_nxdnQueueSizeBytes(1488U), // 31 frames m_authoritative(true), m_supervisor(false), m_dmrBeaconDurationTimer(1000U), m_dmrDedicatedTxTestTimer(1000U, 0U, 125U), m_p25BcastDurationTimer(1000U), m_p25DedicatedTxTestTimer(1000U, 0U, 125U), m_nxdnBcastDurationTimer(1000U), m_nxdnDedicatedTxTestTimer(1000U, 0U, 125U), m_dmrTx1WatchdogTimer(1000U, 1U), m_dmrTx1LoopMS(0U), m_dmrTx2WatchdogTimer(1000U, 1U), m_dmrTx2LoopMS(0U), m_p25TxWatchdogTimer(1000U, 1U), m_p25TxLoopMS(0U), m_nxdnTxWatchdogTimer(1000U, 1U), m_nxdnTxLoopMS(0U), m_mainLoopStage(0U), m_mainLoopMS(0U), m_mainLoopWatchdogTimer(1000U, 1U), m_adjSiteLoopMS(0U), m_adjSiteLoopWatchdogTimer(1000U, 1U), m_dmr1OverflowCnt(0U), m_dmr2OverflowCnt(0U), m_p25OverflowCnt(0U), m_nxdnOverflowCnt(0U), m_disableWatchdogOverflow(false), m_restAddress("0.0.0.0"), m_restPort(REST_API_DEFAULT_PORT), m_RESTAPI(nullptr), m_dmr(nullptr), m_p25(nullptr), m_nxdn(nullptr) { /* stub */ } /* Finalizes a instance of the Host class. */ Host::~Host() = default; /* Executes the main modem host processing loop. */ int Host::run() { bool ret = false; try { ret = yaml::Parse(m_conf, m_confFile.c_str()); if (!ret) { ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); } } catch (yaml::OperationException const& e) { ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); } bool m_daemon = m_conf["daemon"].as(false); if (m_daemon && g_foreground) m_daemon = false; // initialize system logging yaml::Node logConf = m_conf["log"]; bool useSyslog = logConf["useSyslog"].as(false); if (g_foreground) useSyslog = false; ret = ::LogInitialise(logConf["filePath"].as(), logConf["fileRoot"].as(), logConf["fileLevel"].as(0U), logConf["displayLevel"].as(0U), false, useSyslog); if (!ret) { ::fatal("unable to open the log file\n"); } ret = ::ActivityLogInitialise(logConf["activityFilePath"].as(), logConf["fileRoot"].as()); if (!ret) { ::fatal("unable to open the activity log file\n"); } #if !defined(_WIN32) // handle POSIX process forking if (m_daemon) { // create new process pid_t pid = ::fork(); if (pid == -1) { ::fprintf(stderr, "%s: Couldn't fork() , exiting\n", g_progExe.c_str()); ::LogFinalise(); ::ActivityLogFinalise(); return EXIT_FAILURE; } else if (pid != 0) { ::LogFinalise(); ::ActivityLogFinalise(); exit(EXIT_SUCCESS); } // create new session and process group if (::setsid() == -1) { ::fprintf(stderr, "%s: Couldn't setsid(), exiting\n", g_progExe.c_str()); ::LogFinalise(); ::ActivityLogFinalise(); return EXIT_FAILURE; } // set the working directory to the root directory if (::chdir("/") == -1) { ::fprintf(stderr, "%s: Couldn't cd /, exiting\n", g_progExe.c_str()); ::LogFinalise(); ::ActivityLogFinalise(); return EXIT_FAILURE; } ::close(STDIN_FILENO); ::close(STDOUT_FILENO); ::close(STDERR_FILENO); } #endif // !defined(_WIN32) ::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Modem Controller\r\n"); // read base parameters from configuration ret = readParams(); if (!ret) return EXIT_FAILURE; // initialize modem ret = createModem(); if (!ret) return EXIT_FAILURE; // is the modem slaved to a remote DVM host? if (m_modemRemote) { ::LogInfoEx(LOG_HOST, "[ OK ] Host is running in remote modem mode"); StopWatch stopWatch; stopWatch.start(); bool killed = false; // main execution loop while (!killed) { if (m_modem->hasLockout() && m_state != HOST_STATE_LOCKOUT) setState(HOST_STATE_LOCKOUT); else if (!m_modem->hasLockout() && m_state == HOST_STATE_LOCKOUT) setState(STATE_IDLE); if (m_modem->hasError() && m_state != HOST_STATE_ERROR) setState(HOST_STATE_ERROR); else if (!m_modem->hasError() && m_state == HOST_STATE_ERROR) setState(STATE_IDLE); uint32_t ms = stopWatch.elapsed(); if (ms > 1U) m_modem->clock(ms); // ------------------------------------------------------ // -- Modem Clocking -- // ------------------------------------------------------ ms = stopWatch.elapsed(); stopWatch.start(); m_modem->clock(ms); if (g_killed) { if (!m_modem->hasTX()) { killed = true; } } m_modeTimer.clock(ms); if (ms < 2U) Thread::sleep(1U); } setState(HOST_STATE_QUIT); return EXIT_SUCCESS; } yaml::Node systemConf = m_conf["system"]; // try to load radio IDs table std::string ridLookupFile = systemConf["radio_id"]["file"].as(); uint32_t ridReloadTime = systemConf["radio_id"]["time"].as(0U); bool ridAcl = systemConf["radio_id"]["acl"].as(false); LogInfo("Radio Id Lookups"); LogInfo(" File: %s", ridLookupFile.length() > 0U ? ridLookupFile.c_str() : "None"); if (ridReloadTime > 0U) LogInfo(" Reload: %u mins", ridReloadTime); LogInfo(" ACL: %s", ridAcl ? "yes" : "no"); m_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, ridAcl); m_ridLookup->read(); // try to load talkgroup IDs table std::string tidLookupFile = systemConf["talkgroup_id"]["file"].as(); uint32_t tidReloadTime = systemConf["talkgroup_id"]["time"].as(0U); bool tidAcl = systemConf["talkgroup_id"]["acl"].as(false); LogInfo("Talkgroup Rule Lookups"); LogInfo(" File: %s", tidLookupFile.length() > 0U ? tidLookupFile.c_str() : "None"); if (tidReloadTime > 0U) LogInfo(" Reload: %u mins", tidReloadTime); LogInfo(" ACL: %s", tidAcl ? "yes" : "no"); m_tidLookup = new TalkgroupRulesLookup(tidLookupFile, tidReloadTime, tidAcl); m_tidLookup->read(); // initialize networking ret = createNetwork(); if (!ret) return EXIT_FAILURE; // set CW parameters if (systemConf["cwId"]["enable"].as(false)) { uint32_t time = systemConf["cwId"]["time"].as(10U); m_cwCallsign = systemConf["cwId"]["callsign"].as(); LogInfo("CW Id Parameters"); LogInfo(" Time: %u mins", time); LogInfo(" Callsign: %s", m_cwCallsign.c_str()); m_cwIdTime = time * 60U; m_cwIdTimer.setTimeout(m_cwIdTime / 2U); m_cwIdTimer.start(); } // for all modes we handle RSSI std::string rssiMappingFile = systemConf["modem"]["rssiMappingFile"].as(); RSSIInterpolator* rssi = new RSSIInterpolator; if (!rssiMappingFile.empty()) { LogInfo("RSSI"); LogInfo(" Mapping File: %s", rssiMappingFile.c_str()); rssi->load(rssiMappingFile); } yaml::Node protocolConf = m_conf["protocols"]; StopWatch stopWatch; stopWatch.start(); // initialize DMR Timer dmrBeaconIntervalTimer(1000U); LogInfo("DMR Parameters"); LogInfo(" Enabled: %s", m_dmrEnabled ? "yes" : "no"); if (m_dmrEnabled) { yaml::Node dmrProtocol = protocolConf["dmr"]; m_dmrBeacons = dmrProtocol["beacons"]["enable"].as(false); m_dmrTSCCData = dmrProtocol["control"]["enable"].as(false); bool dmrCtrlChannel = dmrProtocol["control"]["dedicated"].as(false); bool embeddedLCOnly = dmrProtocol["embeddedLCOnly"].as(false); bool dmrDumpDataPacket = dmrProtocol["dumpDataPacket"].as(false); bool dmrRepeatDataPacket = dmrProtocol["repeatDataPacket"].as(true); bool dmrDumpCsbkData = dmrProtocol["dumpCsbkData"].as(false); bool dumpTAData = dmrProtocol["dumpTAData"].as(true); uint32_t callHang = dmrProtocol["callHang"].as(3U); uint32_t txHang = dmrProtocol["txHang"].as(4U); bool dmrVerbose = dmrProtocol["verbose"].as(true); bool dmrDebug = dmrProtocol["debug"].as(false); uint32_t jitter = m_conf["network"]["jitter"].as(360U); if (txHang > m_rfModeHang) txHang = m_rfModeHang; if (txHang > m_netModeHang) txHang = m_netModeHang; if (callHang > txHang) callHang = txHang; LogInfo(" Embedded LC Only: %s", embeddedLCOnly ? "yes" : "no"); LogInfo(" Dump Talker Alias Data: %s", dumpTAData ? "yes" : "no"); LogInfo(" Dump Packet Data: %s", dmrDumpDataPacket ? "yes" : "no"); LogInfo(" Repeat Packet Data: %s", dmrRepeatDataPacket ? "yes" : "no"); LogInfo(" Dump CSBK Data: %s", dmrDumpCsbkData ? "yes" : "no"); LogInfo(" Call Hang: %us", callHang); LogInfo(" TX Hang: %us", txHang); // forcibly enable beacons when TSCC is enabled but not in dedicated mode if (m_dmrTSCCData && !dmrCtrlChannel && !m_dmrBeacons) { m_dmrBeacons = true; } LogInfo(" Roaming Beacons: %s", m_dmrBeacons ? "yes" : "no"); if (m_dmrBeacons) { uint32_t dmrBeaconInterval = dmrProtocol["beacons"]["interval"].as(60U); uint32_t dmrBeaconDuration = dmrProtocol["beacons"]["duration"].as(3U); LogInfo(" Roaming Beacon Interval: %us", dmrBeaconInterval); LogInfo(" Roaming Beacon Duration: %us", dmrBeaconDuration); m_dmrBeaconDurationTimer.setTimeout(dmrBeaconDuration); dmrBeaconIntervalTimer.setTimeout(dmrBeaconInterval); dmrBeaconIntervalTimer.start(); g_fireDMRBeacon = true; } LogInfo(" TSCC Control: %s", m_dmrTSCCData ? "yes" : "no"); if (m_dmrTSCCData) { LogInfo(" TSCC Control Channel: %s", dmrCtrlChannel ? "yes" : "no"); if (dmrCtrlChannel) { m_dmrCtrlChannel = dmrCtrlChannel; } g_fireDMRBeacon = true; } m_dmr = std::make_unique(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDumpCsbkData, dmrDebug, dmrVerbose); m_dmr->setOptions(m_conf, m_supervisor, m_controlChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true); if (dmrCtrlChannel) { m_dmr->setCCRunning(true); } m_dmrTXTimer.setTimeout(txHang); if (dmrVerbose) { LogInfo(" Verbose: yes"); } if (dmrDebug) { LogInfo(" Debug: yes"); } } // initialize P25 Timer p25BcastIntervalTimer(1000U); LogInfo("P25 Parameters"); LogInfo(" Enabled: %s", m_p25Enabled ? "yes" : "no"); if (m_p25Enabled) { yaml::Node p25Protocol = protocolConf["p25"]; uint32_t tduPreambleCount = p25Protocol["tduPreambleCount"].as(8U); m_p25CCData = p25Protocol["control"]["enable"].as(false); bool p25CtrlChannel = p25Protocol["control"]["dedicated"].as(false); bool p25CtrlBroadcast = p25Protocol["control"]["broadcast"].as(true); bool p25DumpDataPacket = p25Protocol["dumpDataPacket"].as(false); bool p25RepeatDataPacket = p25Protocol["repeatDataPacket"].as(true); bool p25DumpTsbkData = p25Protocol["dumpTsbkData"].as(false); uint32_t callHang = p25Protocol["callHang"].as(3U); bool p25Verbose = p25Protocol["verbose"].as(true); bool p25Debug = p25Protocol["debug"].as(false); LogInfo(" TDU Preamble before Voice/Data: %u", tduPreambleCount); LogInfo(" Dump Packet Data: %s", p25DumpDataPacket ? "yes" : "no"); LogInfo(" Repeat Packet Data: %s", p25RepeatDataPacket ? "yes" : "no"); LogInfo(" Dump TSBK Data: %s", p25DumpTsbkData ? "yes" : "no"); LogInfo(" Call Hang: %us", callHang); LogInfo(" Control: %s", m_p25CCData ? "yes" : "no"); uint32_t p25ControlBcstInterval = p25Protocol["control"]["interval"].as(300U); uint32_t p25ControlBcstDuration = p25Protocol["control"]["duration"].as(1U); if (m_p25CCData) { LogInfo(" Control Broadcast: %s", p25CtrlBroadcast ? "yes" : "no"); LogInfo(" Control Channel: %s", p25CtrlChannel ? "yes" : "no"); if (p25CtrlChannel) { m_p25CtrlChannel = p25CtrlChannel; } else { LogInfo(" Control Broadcast Interval: %us", p25ControlBcstInterval); LogInfo(" Control Broadcast Duration: %us", p25ControlBcstDuration); } m_p25BcastDurationTimer.setTimeout(p25ControlBcstDuration); p25BcastIntervalTimer.setTimeout(p25ControlBcstInterval); p25BcastIntervalTimer.start(); m_p25CtrlBroadcast = p25CtrlBroadcast; if (p25CtrlBroadcast) { g_fireP25Control = true; } } m_p25 = std::make_unique(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem, m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket, p25DumpTsbkData, p25Debug, p25Verbose); m_p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData, m_p25NetId, m_sysId, m_p25RfssId, m_siteId, m_channelId, m_channelNo, true); if (p25CtrlChannel) { m_p25->setCCRunning(true); } if (p25Verbose) { LogInfo(" Verbose: yes"); } if (p25Debug) { LogInfo(" Debug: yes"); } } // initialize NXDN Timer nxdnBcastIntervalTimer(1000U); LogInfo("NXDN Parameters"); LogInfo(" Enabled: %s", m_nxdnEnabled ? "yes" : "no"); if (m_nxdnEnabled) { yaml::Node nxdnProtocol = protocolConf["nxdn"]; m_nxdnCCData = nxdnProtocol["control"]["enable"].as(false); bool nxdnCtrlChannel = nxdnProtocol["control"]["dedicated"].as(false); bool nxdnCtrlBroadcast = nxdnProtocol["control"]["broadcast"].as(true); bool nxdnDumpRcchData = nxdnProtocol["dumpRcchData"].as(false); uint32_t callHang = nxdnProtocol["callHang"].as(3U); bool nxdnVerbose = nxdnProtocol["verbose"].as(true); bool nxdnDebug = nxdnProtocol["debug"].as(false); LogInfo(" Call Hang: %us", callHang); LogInfo(" Control: %s", m_nxdnCCData ? "yes" : "no"); uint32_t nxdnControlBcstInterval = nxdnProtocol["control"]["interval"].as(300U); uint32_t nxdnControlBcstDuration = nxdnProtocol["control"]["duration"].as(1U); if (m_nxdnCCData) { LogInfo(" Control Broadcast: %s", nxdnCtrlBroadcast ? "yes" : "no"); LogInfo(" Control Channel: %s", nxdnCtrlChannel ? "yes" : "no"); if (nxdnCtrlChannel) { m_nxdnCtrlChannel = nxdnCtrlChannel; } else { LogInfo(" Control Broadcast Interval: %us", nxdnControlBcstInterval); LogInfo(" Control Broadcast Duration: %us", nxdnControlBcstDuration); } m_nxdnBcastDurationTimer.setTimeout(nxdnControlBcstDuration); nxdnBcastIntervalTimer.setTimeout(nxdnControlBcstInterval); nxdnBcastIntervalTimer.start(); m_nxdnCtrlBroadcast = nxdnCtrlBroadcast; if (nxdnCtrlBroadcast) { g_fireNXDNControl = true; } } m_nxdn = std::make_unique(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, nxdnDumpRcchData, nxdnDebug, nxdnVerbose); m_nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData, m_siteId, m_sysId, m_channelId, m_channelNo, true); if (nxdnCtrlChannel) { m_nxdn->setCCRunning(true); } if (nxdnVerbose) { LogInfo(" Verbose: yes"); } if (nxdnDebug) { LogInfo(" Debug: yes"); } } /* ** Error Condition Checking */ if (!m_dmrEnabled && !m_p25Enabled && !m_nxdnEnabled) { ::LogError(LOG_HOST, "No protocols enabled? DMR, P25 and/or NXDN must be enabled!"); g_killed = true; } if (m_fixedMode && ((m_dmrEnabled && m_p25Enabled) || (m_dmrEnabled && m_nxdnEnabled) || (m_p25Enabled && m_nxdnEnabled))) { ::LogError(LOG_HOST, "Cannot have DMR, P25 and NXDN when using fixed state! Choose one protocol for fixed state operation."); g_killed = true; } if (m_isModemDFSI && m_dmrEnabled) { ::LogError(LOG_HOST, "Cannot use V.24/DFSI modem with DMR protocol!"); g_killed = true; } if (m_isModemDFSI && m_nxdnEnabled) { ::LogError(LOG_HOST, "Cannot use V.24/DFSI modem with NXDN protocol!"); g_killed = true; } // P25 CC checks if (m_dmrEnabled && m_p25CtrlChannel) { ::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated P25 control!"); g_killed = true; } if (m_nxdnEnabled && m_p25CtrlChannel) { ::LogError(LOG_HOST, "Cannot have NXDN enabled when using dedicated P25 control!"); g_killed = true; } if (!m_fixedMode && m_p25CtrlChannel) { ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated P25 control!"); } if (!m_duplex && m_p25CCData) { ::LogError(LOG_HOST, "Cannot have P25 control and simplex mode at the same time."); g_killed = true; } // DMR TSCC checks if (m_p25Enabled && m_dmrCtrlChannel) { ::LogError(LOG_HOST, "Cannot have P25 enabled when using dedicated DMR TSCC control!"); g_killed = true; } if (m_nxdnEnabled && m_dmrCtrlChannel) { ::LogError(LOG_HOST, "Cannot have NXDN enabled when using dedicated DMR TSCC control!"); g_killed = true; } if (!m_fixedMode && m_dmrCtrlChannel) { ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated DMR TSCC control!"); } if (!m_duplex && m_dmrTSCCData) { ::LogError(LOG_HOST, "Cannot have DMR TSCC control and simplex mode at the same time."); g_killed = true; } // NXDN CC checks if (m_dmrEnabled && m_nxdnCtrlChannel) { ::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated NXDN control!"); g_killed = true; } if (m_p25Enabled && m_nxdnCtrlChannel) { ::LogError(LOG_HOST, "Cannot have P25 enabled when using dedicated NXDN control!"); g_killed = true; } if (!m_fixedMode && m_nxdnCtrlChannel) { ::LogWarning(LOG_HOST, "Fixed mode should be enabled when using dedicated NXDN control!"); } if (!m_duplex && m_nxdnCCData) { ::LogError(LOG_HOST, "Cannot have NXDN control and simplex mode at the same time."); g_killed = true; } // DMR beacon checks if (m_dmrBeacons && m_p25CCData) { ::LogError(LOG_HOST, "Cannot have DMR roaming becaons and P25 control at the same time."); g_killed = true; } if (!m_duplex && m_dmrBeacons) { ::LogError(LOG_HOST, "Cannot have DMR roaming beacons and simplex mode at the same time."); g_killed = true; } bool killed = false; if (!g_killed) { // fixed mode will force a state change if (m_fixedMode) { if (m_dmr != nullptr) setState(STATE_DMR); if (m_p25 != nullptr) setState(STATE_P25); if (m_nxdn != nullptr) setState(STATE_NXDN); } else { if (m_dmrCtrlChannel) { m_fixedMode = true; setState(STATE_DMR); } if (m_p25CtrlChannel) { m_fixedMode = true; setState(STATE_P25); } if (m_isModemDFSI) { m_fixedMode = true; setState(STATE_P25); } if (m_nxdnCtrlChannel) { m_fixedMode = true; setState(STATE_NXDN); } setState(STATE_IDLE); } if (m_RESTAPI != nullptr) { m_RESTAPI->setProtocols(m_dmr.get(), m_p25.get(), m_nxdn.get()); } ::LogInfoEx(LOG_HOST, "[WAIT] Host is performing late initialization and warmup"); m_modem->clearNXDNFrame(); m_modem->clearP25Frame(); m_modem->clearDMRFrame2(); m_modem->clearDMRFrame1(); // perform early pumping of the modem clock (this is so the DSP has time to setup its buffers), // and clock the network (so it may perform early connect) uint32_t elapsedMs = 0U; while (!g_killed) { uint32_t ms = stopWatch.elapsed(); stopWatch.start(); elapsedMs += ms; m_modem->clock(ms); if (m_network != nullptr) m_network->clock(ms); if (m_p25 != nullptr) m_p25->reset(); if (m_nxdn != nullptr) m_nxdn->reset(); Thread::sleep(IDLE_WARMUP_MS); if (elapsedMs > 15000U) break; } // check if the modem is a hotspot (this check must always be done after late init) if (m_modem->isHotspot()) { if ((m_dmrEnabled && m_p25Enabled) || (m_nxdnEnabled && m_dmrEnabled) || (m_nxdnEnabled && m_p25Enabled)) { ::LogError(LOG_HOST, "Multi-mode (DMR, P25 and NXDN) is not supported for hotspots!"); g_killed = true; killed = true; } else { if (!m_fixedMode) { ::LogInfoEx(LOG_HOST, "Host is running on a hotspot modem! Fixed mode is forced."); m_fixedMode = true; } } } stopWatch.start(); } else { return EXIT_SUCCESS; } bool hasTxShutdown = false; // Macro to start DMR duplex idle transmission (or beacon) #define START_DMR_DUPLEX_IDLE(x) \ if (m_dmr != nullptr) { \ if (m_duplex) { \ m_modem->writeDMRStart(x); \ m_dmrTXTimer.start(); \ } \ } /* ** Initialize Threads */ /** Watchdog */ if (!Thread::runAsThread(this, threadWatchdog)) return EXIT_FAILURE; /** Modem */ if (!Thread::runAsThread(this, threadModem)) return EXIT_FAILURE; /** Digital Mobile Radio Frame Processor */ if (m_dmr != nullptr) { if (!Thread::runAsThread(this, threadDMRReader1)) return EXIT_FAILURE; if (!Thread::runAsThread(this, threadDMRWriter1)) return EXIT_FAILURE; if (!Thread::runAsThread(this, threadDMRReader2)) return EXIT_FAILURE; if (!Thread::runAsThread(this, threadDMRWriter2)) return EXIT_FAILURE; } /** Project 25 Frame Processor */ if (m_p25 != nullptr) { if (!Thread::runAsThread(this, threadP25Reader)) return EXIT_FAILURE; if (!Thread::runAsThread(this, threadP25Writer)) return EXIT_FAILURE; } /** Next Generation Digital Narrowband Frame Processor */ if (m_nxdn != nullptr) { if (!Thread::runAsThread(this, threadNXDNReader)) return EXIT_FAILURE; if (!Thread::runAsThread(this, threadNXDNWriter)) return EXIT_FAILURE; } /** Adjacent Site and Affiliation Update */ if (!Thread::runAsThread(this, threadSiteData)) return EXIT_FAILURE; /** Network Presence Notification */ { if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr) { if (!Thread::runAsThread(this, threadPresence)) return EXIT_FAILURE; } if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) { if (!Thread::runAsThread(this, threadPresence)) return EXIT_FAILURE; } } /* ** Main execution loop */ #if defined(_WIN32) ::LogInfoEx(LOG_HOST, "[ OK ] Host is up and running on Win32"); #else struct utsname utsinfo; ::memset(&utsinfo, 0, sizeof(utsinfo)); ::uname(&utsinfo); ::LogInfoEx(LOG_HOST, "[ OK ] Host is up and running on %s %s %s", utsinfo.sysname, utsinfo.release, utsinfo.machine); #endif // defined(_WIN32) while (!killed) { if (m_modem->hasLockout() && m_state != HOST_STATE_LOCKOUT) setState(HOST_STATE_LOCKOUT); else if (!m_modem->hasLockout() && m_state == HOST_STATE_LOCKOUT) setState(STATE_IDLE); if (m_modem->hasError() && m_state != HOST_STATE_ERROR) setState(HOST_STATE_ERROR); else if (!m_modem->hasError() && m_state == HOST_STATE_ERROR) setState(STATE_IDLE); uint32_t ms = stopWatch.elapsed(); stopWatch.start(); if (!m_modem->hasError()) { if (!m_fixedMode) { if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) { setState(STATE_IDLE); } } else { m_modeTimer.stop(); if (m_dmr != nullptr && m_state != STATE_DMR && !m_modem->hasTX()) { LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_DMR); setState(STATE_DMR); } if (m_p25 != nullptr && m_state != STATE_P25 && !m_modem->hasTX()) { LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_P25); setState(STATE_P25); } if (m_nxdn != nullptr && m_state != STATE_NXDN && !m_modem->hasTX()) { LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_NXDN); setState(STATE_NXDN); } } } m_mainLoopWatchdogTimer.start(); m_mainLoopStage = 0U; // intentional magic number m_mainLoopMS = ms; // ------------------------------------------------------ // -- Network, DMR, and P25 Clocking -- // ------------------------------------------------------ if (m_network != nullptr) { m_mainLoopStage = 5U; // intentional magic number m_network->clock(ms); } if (m_dmr != nullptr) { m_mainLoopStage = 6U; // intentional magic number m_dmr->clock(); if (m_dmrCtrlChannel) { if (!m_dmrDedicatedTxTestTimer.isRunning()) { m_dmrDedicatedTxTestTimer.start(); } else { m_dmrDedicatedTxTestTimer.clock(ms); } } } if (m_p25 != nullptr) { m_mainLoopStage = 7U; // intentional magic number m_p25->clock(); if (m_p25CtrlChannel) { if (!m_p25DedicatedTxTestTimer.isRunning()) { m_p25DedicatedTxTestTimer.start(); } else { m_p25DedicatedTxTestTimer.clock(ms); } } } if (m_nxdn != nullptr) { m_mainLoopStage = 8U; // intentional magic number m_nxdn->clock(); if (m_nxdnCtrlChannel) { if (!m_nxdnDedicatedTxTestTimer.isRunning()) { m_nxdnDedicatedTxTestTimer.start(); } else { m_nxdnDedicatedTxTestTimer.clock(ms); } } } if (m_udpDSFIRemotePort != nullptr) { m_mainLoopStage = 11U; // intentional magic number modem::port::specialized::V24UDPPort* udpPort = dynamic_cast(m_udpDSFIRemotePort); udpPort->clock(ms); } // ------------------------------------------------------ // -- Timer Clocking -- // ------------------------------------------------------ // clock and check CW timer m_cwIdTimer.clock(ms); if (m_cwIdTimer.isRunning() && m_cwIdTimer.hasExpired()) { m_mainLoopStage = 9U; // intentional magic number if (!m_modem->hasTX() && !m_p25CtrlChannel && !m_dmrCtrlChannel && !m_nxdnCtrlChannel) { if (m_dmrBeaconDurationTimer.isRunning() || m_p25BcastDurationTimer.isRunning() || m_nxdnBcastDurationTimer.isRunning()) { LogDebug(LOG_HOST, "CW, beacon or CC timer running, ceasing"); m_dmrBeaconDurationTimer.stop(); m_p25BcastDurationTimer.stop(); m_nxdnBcastDurationTimer.stop(); } LogMessage(LOG_HOST, "CW, start transmitting"); m_isTxCW = true; std::lock_guard lock(m_clockingMutex); setState(STATE_IDLE); m_modem->sendCWId(m_cwCallsign); Thread::sleep(CW_IDLE_SLEEP_MS); bool first = true; do { // ------------------------------------------------------ // -- Modem Clocking -- // ------------------------------------------------------ ms = stopWatch.elapsed(); stopWatch.start(); m_mainLoopWatchdogTimer.start(); m_mainLoopMS = ms; m_modem->clock(ms); if (!first && !m_modem->hasTX()) { LogMessage(LOG_HOST, "CW, finished transmitting"); break; } if (first) { first = false; Thread::sleep(200U + CW_IDLE_SLEEP_MS); // ~250ms; poll time of the modem } else Thread::sleep(CW_IDLE_SLEEP_MS); } while (true); m_isTxCW = false; m_cwIdTimer.setTimeout(m_cwIdTime); m_cwIdTimer.start(); } } m_mainLoopStage = 10U; // intentional magic number /** Digial Mobile Radio */ if (m_dmr != nullptr) { if (m_dmrTSCCData && m_dmrCtrlChannel) { if (m_state != STATE_DMR) setState(STATE_DMR); if (!m_modem->hasTX()) { START_DMR_DUPLEX_IDLE(true); } } // clock and check DMR roaming beacon interval timer dmrBeaconIntervalTimer.clock(ms); if ((dmrBeaconIntervalTimer.isRunning() && dmrBeaconIntervalTimer.hasExpired()) || g_fireDMRBeacon) { if ((m_state == STATE_IDLE || m_state == STATE_DMR) && !m_modem->hasTX()) { if (m_modeTimer.isRunning()) { m_modeTimer.stop(); } if (m_state != STATE_DMR) setState(STATE_DMR); if (m_fixedMode) { START_DMR_DUPLEX_IDLE(true); } if (m_dmrTSCCData) { m_dmr->setCCRunning(true); } g_fireDMRBeacon = false; if (m_dmrTSCCData) { LogMessage(LOG_HOST, "DMR, start CC broadcast"); } else { LogMessage(LOG_HOST, "DMR, roaming beacon burst"); } dmrBeaconIntervalTimer.start(); m_dmrBeaconDurationTimer.start(); } } // clock and check DMR roaming beacon duration timer m_dmrBeaconDurationTimer.clock(ms); if (m_dmrBeaconDurationTimer.isRunning() && m_dmrBeaconDurationTimer.hasExpired()) { m_dmrBeaconDurationTimer.stop(); if (!m_fixedMode) { if (m_state == STATE_DMR && !m_modeTimer.isRunning()) { m_modeTimer.setTimeout(m_rfModeHang); m_modeTimer.start(); } } if (m_dmrTSCCData) { m_dmr->setCCRunning(false); } } // clock and check DMR Tx timer if (!m_dmrCtrlChannel) { m_dmrTXTimer.clock(ms); if (m_dmrTXTimer.isRunning() && m_dmrTXTimer.hasExpired()) { m_modem->writeDMRStart(false); m_modem->clearDMRFrame1(); m_modem->clearDMRFrame2(); m_dmrTXTimer.stop(); } } else { m_dmrTXTimer.stop(); } } /** Project 25 */ if (m_p25 != nullptr) { if (m_p25CCData) { p25BcastIntervalTimer.clock(ms); if (!m_p25CtrlChannel && m_p25CtrlBroadcast) { // clock and check P25 CC broadcast interval timer if ((p25BcastIntervalTimer.isRunning() && p25BcastIntervalTimer.hasExpired()) || g_fireP25Control) { if ((m_state == STATE_IDLE || m_state == STATE_P25) && !m_modem->hasTX()) { if (m_modeTimer.isRunning()) { m_modeTimer.stop(); } if (m_state != STATE_P25) setState(STATE_P25); m_p25->setCCRunning(true); // hide this message for continuous CC -- otherwise display every time we process if (!m_p25CtrlChannel) { LogMessage(LOG_HOST, "P25, start CC broadcast"); } g_fireP25Control = false; p25BcastIntervalTimer.start(); m_p25BcastDurationTimer.start(); // if the CC is continuous -- clock one cycle into the duration timer if (m_p25CtrlChannel) { m_p25BcastDurationTimer.clock(ms); } } } if (m_p25BcastDurationTimer.isPaused()) { m_p25BcastDurationTimer.resume(); } // clock and check P25 CC broadcast duration timer m_p25BcastDurationTimer.clock(ms); if (m_p25BcastDurationTimer.isRunning() && m_p25BcastDurationTimer.hasExpired()) { m_p25BcastDurationTimer.stop(); m_p25->setCCRunning(false); if (m_state == STATE_P25 && !m_modeTimer.isRunning()) { m_modeTimer.setTimeout(m_rfModeHang); m_modeTimer.start(); } } } } } /** Next Generation Digital Narrowband */ if (m_nxdn != nullptr) { if (m_nxdnCCData) { nxdnBcastIntervalTimer.clock(ms); if (!m_nxdnCtrlChannel && m_nxdnCtrlBroadcast) { // clock and check NXDN CC broadcast interval timer if ((nxdnBcastIntervalTimer.isRunning() && nxdnBcastIntervalTimer.hasExpired()) || g_fireNXDNControl) { if ((m_state == STATE_IDLE || m_state == STATE_NXDN) && !m_modem->hasTX()) { if (m_modeTimer.isRunning()) { m_modeTimer.stop(); } if (m_state != STATE_NXDN) setState(STATE_NXDN); m_nxdn->setCCRunning(true); // hide this message for continuous CC -- otherwise display every time we process if (!m_nxdnCtrlChannel) { LogMessage(LOG_HOST, "NXDN, start CC broadcast"); } g_fireNXDNControl = false; nxdnBcastIntervalTimer.start(); m_nxdnBcastDurationTimer.start(); // if the CC is continuous -- clock one cycle into the duration timer if (m_nxdnCtrlChannel) { m_nxdnBcastDurationTimer.clock(ms); } } } if (m_nxdnBcastDurationTimer.isPaused()) { m_nxdnBcastDurationTimer.resume(); } // clock and check NXDN CC broadcast duration timer m_nxdnBcastDurationTimer.clock(ms); if (m_nxdnBcastDurationTimer.isRunning() && m_nxdnBcastDurationTimer.hasExpired()) { m_nxdnBcastDurationTimer.stop(); m_nxdn->setCCRunning(false); if (m_state == STATE_NXDN && !m_modeTimer.isRunning()) { m_modeTimer.setTimeout(m_rfModeHang); m_modeTimer.start(); } } } else { // simply use the NXDN CC interval timer in a non-broadcast state to transmit adjacent site data over // the network if (nxdnBcastIntervalTimer.isRunning() && nxdnBcastIntervalTimer.hasExpired()) { if ((m_state == STATE_IDLE || m_state == STATE_NXDN) && !m_modem->hasTX()) { nxdnBcastIntervalTimer.start(); } } } } } if (g_killed) { if (m_dmr != nullptr) { if (m_state == STATE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } if (m_dmrCtrlChannel) { if (!hasTxShutdown) { m_modem->clearDMRFrame1(); m_modem->clearDMRFrame2(); } m_dmr->setCCRunning(false); m_dmr->setCCHalted(true); m_dmrBeaconDurationTimer.stop(); dmrBeaconIntervalTimer.stop(); } } if (m_p25 != nullptr) { if (m_p25CtrlChannel) { if (!hasTxShutdown) { m_modem->clearP25Frame(); m_p25->reset(); } m_p25->setCCRunning(false); m_p25BcastDurationTimer.stop(); p25BcastIntervalTimer.stop(); } } if (m_nxdn != nullptr) { if (m_nxdnCtrlChannel) { if (!hasTxShutdown) { m_modem->clearNXDNFrame(); m_nxdn->reset(); } m_nxdn->setCCRunning(false); m_nxdnBcastDurationTimer.stop(); nxdnBcastIntervalTimer.stop(); } } hasTxShutdown = true; killed = true; } m_modeTimer.clock(ms); if ((m_state != STATE_IDLE) && ms <= m_activeTickDelay) Thread::sleep(m_activeTickDelay); if (m_state == STATE_IDLE) Thread::sleep(m_idleTickDelay); } if (rssi != nullptr) { delete rssi; } setState(HOST_STATE_QUIT); return EXIT_SUCCESS; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /* Helper to generate the status of the host in JSON format. */ json::object Host::getStatus() { json::object response = json::object(); yaml::Node systemConf = m_conf["system"]; yaml::Node networkConf = m_conf["network"]; { response["state"].set(m_state); response["isTxCW"].set(m_isTxCW); response["fixedMode"].set(m_fixedMode); response["dmrTSCCEnable"].set(m_dmrTSCCData); response["dmrCC"].set(m_dmrCtrlChannel); response["p25CtrlEnable"].set(m_p25CCData); response["p25CC"].set(m_p25CtrlChannel); response["nxdnCtrlEnable"].set(m_nxdnCCData); response["nxdnCC"].set(m_nxdnCtrlChannel); yaml::Node p25Protocol = m_conf["protocols"]["p25"]; yaml::Node nxdnProtocol = m_conf["protocols"]["nxdn"]; response["tx"].set(m_modem->m_tx); response["channelId"].set(m_channelId); response["channelNo"].set(m_channelNo); response["lastDstId"].set(m_lastDstId); response["lastSrcId"].set(m_lastSrcId); uint32_t peerId = networkConf["id"].as(); response["peerId"].set(peerId); response["sysId"].set(m_sysId); response["siteId"].set(m_siteId); response["p25RfssId"].set(m_p25RfssId); response["p25NetId"].set(m_p25NetId); response["p25NAC"].set(m_p25NAC); } yaml::Node modemConfig = m_conf["system"]["modem"]; { json::object modemInfo = json::object(); std::string portType = modemConfig["protocol"]["type"].as(); modemInfo["portType"].set(portType); yaml::Node uartConfig = modemConfig["protocol"]["uart"]; std::string modemPort = uartConfig["port"].as(); modemInfo["modemPort"].set(modemPort); uint32_t portSpeed = uartConfig["speed"].as(115200U); modemInfo["portSpeed"].set(portSpeed); if (!m_modem->isHotspot()) { modemInfo["pttInvert"].set(m_modem->m_pttInvert); modemInfo["rxInvert"].set(m_modem->m_rxInvert); modemInfo["txInvert"].set(m_modem->m_txInvert); modemInfo["dcBlocker"].set(m_modem->m_dcBlocker); } modemInfo["rxLevel"].set(m_modem->m_rxLevel); modemInfo["cwTxLevel"].set(m_modem->m_cwIdTXLevel); modemInfo["dmrTxLevel"].set(m_modem->m_dmrTXLevel); modemInfo["p25TxLevel"].set(m_modem->m_p25TXLevel); modemInfo["nxdnTxLevel"].set(m_modem->m_nxdnTXLevel); modemInfo["rxDCOffset"].set(m_modem->m_rxDCOffset); modemInfo["txDCOffset"].set(m_modem->m_txDCOffset); if (!m_modem->isHotspot()) { modemInfo["dmrSymLevel3Adj"].set(m_modem->m_dmrSymLevel3Adj); modemInfo["dmrSymLevel1Adj"].set(m_modem->m_dmrSymLevel1Adj); modemInfo["p25SymLevel3Adj"].set(m_modem->m_p25SymLevel3Adj); modemInfo["p25SymLevel1Adj"].set(m_modem->m_p25SymLevel1Adj); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { modemInfo["nxdnSymLevel3Adj"].set(m_modem->m_nxdnSymLevel3Adj); modemInfo["nxdnSymLevel1Adj"].set(m_modem->m_nxdnSymLevel1Adj); } } if (m_modem->isHotspot()) { modemInfo["dmrDiscBW"].set(m_modem->m_dmrDiscBWAdj); modemInfo["dmrPostBW"].set(m_modem->m_dmrPostBWAdj); modemInfo["p25DiscBW"].set(m_modem->m_p25DiscBWAdj); modemInfo["p25PostBW"].set(m_modem->m_p25PostBWAdj); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { modemInfo["nxdnDiscBW"].set(m_modem->m_nxdnDiscBWAdj); modemInfo["nxdnPostBW"].set(m_modem->m_nxdnPostBWAdj); modemInfo["afcEnabled"].set(m_modem->m_afcEnable); modemInfo["afcKI"].set(m_modem->m_afcKI); modemInfo["afcKP"].set(m_modem->m_afcKP); modemInfo["afcRange"].set(m_modem->m_afcRange); } switch (m_modem->m_adfGainMode) { case ADF_GAIN_AUTO_LIN: modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto High Linearity"); break; case ADF_GAIN_LOW: modemInfo["gainMode"].set("ADF7021 Gain Mode: Low"); break; case ADF_GAIN_HIGH: modemInfo["gainMode"].set("ADF7021 Gain Mode: High"); break; case ADF_GAIN_AUTO: default: modemInfo["gainMode"].set("ADF7021 Gain Mode: Auto"); break; } } modemInfo["fdmaPreambles"].set(m_modem->m_fdmaPreamble); modemInfo["dmrRxDelay"].set(m_modem->m_dmrRxDelay); modemInfo["p25CorrCount"].set(m_modem->m_p25CorrCount); modemInfo["rxFrequency"].set(m_modem->m_rxFrequency); modemInfo["txFrequency"].set(m_modem->m_txFrequency); modemInfo["rxTuning"].set(m_modem->m_rxTuning); modemInfo["txTuning"].set(m_modem->m_txTuning); uint32_t rxFreqEffective = m_modem->m_rxFrequency + m_modem->m_rxTuning; modemInfo["rxFrequencyEffective"].set(rxFreqEffective); uint32_t txFreqEffective = m_modem->m_txFrequency + m_modem->m_txTuning; modemInfo["txFrequencyEffective"].set(txFreqEffective); uint8_t protoVer = m_modem->getVersion(); modemInfo["protoVer"].set(protoVer); response["modem"].set(modemInfo); } return response; } /* Modem port open callback. */ bool Host::rmtPortModemOpen(Modem* modem) { assert(m_modemRemotePort != nullptr); bool ret = m_modemRemotePort->open(); if (!ret) return false; LogMessage(LOG_MODEM, "Modem Ready [Remote Mode]"); // handled modem open return true; } /* Modem port close callback. */ bool Host::rmtPortModemClose(Modem* modem) { assert(m_modemRemotePort != nullptr); m_modemRemotePort->close(); // handled modem close return true; } /* Modem clock callback. */ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len) { assert(m_modemRemotePort != nullptr); if (rspType == RTM_OK && len > 0U) { if (modem->getTrace()) Utils::dump(1U, "TX Remote Data", buffer, len); // send entire modem packet over the remote port m_modemRemotePort->write(buffer, len); } // read any data from the remote port for the air interface uint8_t data[BUFFER_LENGTH]; ::memset(data, 0x00U, BUFFER_LENGTH); uint32_t ret = m_modemRemotePort->read(data, BUFFER_LENGTH); if (ret > 0) { if (modem->getTrace()) Utils::dump(1U, "RX Remote Data", (uint8_t*)data, ret); if (ret < 3U) { LogError(LOG_MODEM, "Illegal length of remote data must be >3 bytes"); Utils::dump("Buffer dump", data, ret); // handled modem response return true; } uint8_t len = data[1U]; int ret = modem->write(data, len); if (ret != int(len)) LogError(LOG_MODEM, "Error writing remote data"); } // handled modem response return true; } /* Helper to set the host/modem running state. */ void Host::setState(uint8_t state) { assert(m_modem != nullptr); //if (m_state != state) { // LogDebug(LOG_HOST, "setState, m_state = %u, state = %u", m_state, state); //} switch (state) { case STATE_DMR: m_modem->setState(STATE_DMR); m_state = STATE_DMR; m_modeTimer.start(); //m_cwIdTimer.stop(); break; case STATE_P25: m_modem->setState(STATE_P25); m_state = STATE_P25; m_modeTimer.start(); //m_cwIdTimer.stop(); break; case STATE_NXDN: m_modem->setState(STATE_NXDN); m_state = STATE_NXDN; m_modeTimer.start(); //m_cwIdTimer.stop(); break; case HOST_STATE_LOCKOUT: LogWarning(LOG_HOST, "Mode change, HOST_STATE_LOCKOUT"); if (m_network != nullptr) m_network->enable(false); if (m_state == STATE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } m_modem->setState(STATE_IDLE); m_state = HOST_STATE_LOCKOUT; m_modeTimer.stop(); //m_cwIdTimer.stop(); break; case HOST_STATE_ERROR: LogWarning(LOG_HOST, "Mode change, HOST_STATE_ERROR"); if (m_network != nullptr) m_network->enable(false); if (m_state == STATE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } m_state = HOST_STATE_ERROR; m_modeTimer.stop(); m_cwIdTimer.stop(); break; default: if (m_network != nullptr) m_network->enable(true); if (m_state == STATE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } m_modem->setState(STATE_IDLE); m_modem->clearDMRFrame1(); m_modem->clearDMRFrame2(); m_modem->clearP25Frame(); if (m_state == HOST_STATE_ERROR) { m_modem->sendCWId(m_cwCallsign); m_cwIdTimer.setTimeout(m_cwIdTime); m_cwIdTimer.start(); } m_modeTimer.stop(); if (m_state == HOST_STATE_QUIT) { ::LogInfoEx(LOG_HOST, "Host is shutting down"); if (m_modem != nullptr) { m_modem->close(); delete m_modem; } ::LogSetNetwork(nullptr); if (m_network != nullptr) { m_network->close(); delete m_network; } if (m_RESTAPI != nullptr) { m_RESTAPI->close(); delete m_RESTAPI; } if (m_channelLookup != nullptr) { delete m_channelLookup; } if (m_tidLookup != nullptr) { m_tidLookup->stop(); delete m_tidLookup; } if (m_ridLookup != nullptr) { m_ridLookup->stop(); delete m_ridLookup; } } else { m_state = STATE_IDLE; } break; } } /* Entry point to modem clock thread. */ void* Host::threadModem(void* arg) { thread_t* th = (thread_t*)arg; if (th != nullptr) { #if defined(_WIN32) ::CloseHandle(th->thread); #else ::pthread_detach(th->thread); #endif // defined(_WIN32) std::string threadName("host:modem"); Host* host = static_cast(th->obj); if (host == nullptr) { g_killed = true; LogDebug(LOG_HOST, "[FAIL] %s", threadName.c_str()); } if (g_killed) { delete th; return nullptr; } LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE StopWatch stopWatch; stopWatch.start(); while (!g_killed) { // scope is intentional { std::lock_guard lock(m_clockingMutex); // ------------------------------------------------------ // -- Modem Clocking -- // ------------------------------------------------------ uint32_t ms = stopWatch.elapsed(); stopWatch.start(); host->m_modem->clock(ms); } if (host->m_state != STATE_IDLE) Thread::sleep(m_activeTickDelay); if (host->m_state == STATE_IDLE) Thread::sleep(m_idleTickDelay); } LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } return nullptr; } /* Entry point to watchdog thread. */ void* Host::threadWatchdog(void* arg) { thread_t* th = (thread_t*)arg; if (th != nullptr) { #if defined(_WIN32) ::CloseHandle(th->thread); #else ::pthread_detach(th->thread); #endif // defined(_WIN32) std::string threadName("host:watchdog"); Host* host = static_cast(th->obj); if (host == nullptr) { g_killed = true; LogDebug(LOG_HOST, "[FAIL] %s", threadName.c_str()); } if (g_killed) { delete th; return nullptr; } LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE StopWatch stopWatch; stopWatch.start(); while (!g_killed) { uint32_t ms = stopWatch.elapsed(); stopWatch.start(); // scope is intentional { /** Digital Mobile Radio */ if (host->m_dmr != nullptr) { if (host->m_dmrTx1WatchdogTimer.isRunning()) host->m_dmrTx1WatchdogTimer.clock(ms); if (host->m_dmrTx1WatchdogTimer.isRunning() && host->m_dmrTx1WatchdogTimer.hasExpired() && !host->m_dmrTx1WatchdogTimer.isPaused()) { host->m_dmrTx1WatchdogTimer.pause(); LogError(LOG_HOST, "PANIC; DMR, slot 1 Tx frame processor hung >%us, ms = %u", host->m_dmrTx1WatchdogTimer.getTimeout(), host->m_dmrTx1LoopMS); } if (!host->m_disableWatchdogOverflow) { if (host->m_modem->gotModemStatus() && !host->m_modem->hasDMRSpace1() && host->m_dmr->isQueueFull(1U) && !host->m_dmrCtrlChannel && !host->m_dmrBeaconDurationTimer.isRunning()) { if (host->m_dmr1OverflowCnt > MAX_OVERFLOW_CNT) { std::lock_guard lock(m_clockingMutex); LogError(LOG_HOST, "PANIC; DMR, has no DMR slot 1 FIFO space, and DMR slot 1 queue is full! Resetting states."); host->setState(STATE_IDLE); host->m_modem->writeDMRStart(false); host->m_modem->clearDMRFrame1(); // respect the fixed mode state if (host->m_fixedMode) { host->setState(STATE_DMR); } host->m_dmr1OverflowCnt = 0U; } else { host->m_dmr1OverflowCnt++; } } else { host->m_dmr1OverflowCnt = 0U; } } if (host->m_dmrTx2WatchdogTimer.isRunning()) host->m_dmrTx2WatchdogTimer.clock(ms); if (host->m_dmrTx2WatchdogTimer.isRunning() && host->m_dmrTx2WatchdogTimer.hasExpired() && !host->m_dmrTx2WatchdogTimer.isPaused()) { host->m_dmrTx2WatchdogTimer.pause(); LogError(LOG_HOST, "PANIC; DMR, slot 2 Tx frame processor hung >%us, ms = %u", host->m_dmrTx2WatchdogTimer.getTimeout(), host->m_dmrTx2LoopMS); } if (!host->m_disableWatchdogOverflow) { if (host->m_modem->gotModemStatus() && !host->m_modem->hasDMRSpace2() && host->m_dmr->isQueueFull(2U) && !host->m_dmrCtrlChannel && !host->m_dmrBeaconDurationTimer.isRunning()) { if (host->m_dmr2OverflowCnt > MAX_OVERFLOW_CNT) { std::lock_guard lock(m_clockingMutex); LogError(LOG_HOST, "PANIC; DMR, has no DMR slot 2 FIFO space, and DMR slot 2 queue is full! Resetting states."); host->setState(STATE_IDLE); host->m_modem->writeDMRStart(false); host->m_modem->clearDMRFrame2(); // respect the fixed mode state if (host->m_fixedMode) { host->setState(STATE_DMR); } host->m_dmr2OverflowCnt = 0U; } else { host->m_dmr2OverflowCnt++; } } else { host->m_dmr2OverflowCnt = 0U; } } } /** Project 25 */ if (host->m_p25 != nullptr) { if (host->m_p25TxWatchdogTimer.isRunning()) host->m_p25TxWatchdogTimer.clock(ms); if (host->m_p25TxWatchdogTimer.isRunning() && host->m_p25TxWatchdogTimer.hasExpired() && !host->m_p25TxWatchdogTimer.isPaused()) { host->m_p25TxWatchdogTimer.pause(); LogError(LOG_HOST, "PANIC; P25, Tx frame processor hung >%us, ms = %u", host->m_p25TxWatchdogTimer.getTimeout(), host->m_p25TxLoopMS); } if (!host->m_disableWatchdogOverflow) { if (host->m_modem->gotModemStatus() && !host->m_modem->hasP25Space(P25DEF::P25_LDU_FRAME_LENGTH_BYTES) && host->m_p25->isQueueFull() && !host->m_p25CtrlChannel && !host->m_p25BcastDurationTimer.isRunning()) { if (host->m_p25OverflowCnt > MAX_OVERFLOW_CNT) { std::lock_guard lock(m_clockingMutex); LogError(LOG_HOST, "PANIC; P25, modem has no P25 FIFO space, and internal P25 queue is full! Resetting states."); host->setState(STATE_IDLE); host->m_modem->clearP25Frame(); host->m_p25->reset(); // respect the fixed mode state if (host->m_fixedMode) { host->setState(STATE_P25); } host->m_p25OverflowCnt = 0U; } else { host->m_p25OverflowCnt++; } } else { host->m_p25OverflowCnt = 0U; } } } /** Next Generation Digital Narrowband */ if (host->m_nxdn != nullptr) { if (host->m_nxdnTxWatchdogTimer.isRunning()) host->m_nxdnTxWatchdogTimer.clock(ms); if (host->m_nxdnTxWatchdogTimer.isRunning() && host->m_nxdnTxWatchdogTimer.hasExpired() && !host->m_nxdnTxWatchdogTimer.isPaused()) { host->m_nxdnTxWatchdogTimer.pause(); LogError(LOG_HOST, "PANIC; NXDN, Tx frame processor hung >%us, ms = %u", host->m_nxdnTxWatchdogTimer.getTimeout(), host->m_nxdnTxLoopMS); } if (!host->m_disableWatchdogOverflow) { if (host->m_modem->gotModemStatus() && !host->m_modem->hasNXDNSpace() && host->m_nxdn->isQueueFull() && !host->m_nxdnCtrlChannel && !host->m_nxdnBcastDurationTimer.isRunning()) { if (host->m_nxdnOverflowCnt > MAX_OVERFLOW_CNT) { std::lock_guard lock(m_clockingMutex); LogError(LOG_HOST, "PANIC; NXDN, modem has no NXDN FIFO space, and NXDN queue is full! Resetting states."); host->setState(STATE_IDLE); host->m_modem->clearNXDNFrame(); host->m_nxdn->reset(); // respect the fixed mode state if (host->m_fixedMode) { host->setState(STATE_NXDN); } host->m_nxdnOverflowCnt = 0U; } else { host->m_nxdnOverflowCnt++; } } else { host->m_nxdnOverflowCnt = 0U; } } } } // scope is intentional { if (host->m_mainLoopWatchdogTimer.isRunning()) host->m_mainLoopWatchdogTimer.clock(ms); if (host->m_mainLoopWatchdogTimer.isRunning() && host->m_mainLoopWatchdogTimer.hasExpired() && !host->m_mainLoopWatchdogTimer.isPaused()) { host->m_mainLoopWatchdogTimer.pause(); LogError(LOG_HOST, "main processor hung >%us, stage = %u, ms = %u", host->m_mainLoopWatchdogTimer.getTimeout(), host->m_mainLoopStage, host->m_mainLoopMS); } if (host->m_adjSiteLoopWatchdogTimer.isRunning()) host->m_adjSiteLoopWatchdogTimer.clock(ms); if (host->m_adjSiteLoopWatchdogTimer.isRunning() && host->m_adjSiteLoopWatchdogTimer.hasExpired() && !host->m_adjSiteLoopWatchdogTimer.isPaused()) { host->m_adjSiteLoopWatchdogTimer.pause(); LogError(LOG_HOST, "adj. site update hung >%us, ms = %u", host->m_adjSiteLoopWatchdogTimer.getTimeout(), host->m_adjSiteLoopMS); } } if (host->m_state != STATE_IDLE) Thread::sleep(m_activeTickDelay); if (host->m_state == STATE_IDLE) Thread::sleep(m_idleTickDelay); } LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } return nullptr; } /* Entry point to site data update thread. */ void* Host::threadSiteData(void* arg) { thread_t* th = (thread_t*)arg; if (th != nullptr) { #if defined(_WIN32) ::CloseHandle(th->thread); #else ::pthread_detach(th->thread); #endif // defined(_WIN32) std::string threadName("host:site-data"); Host* host = static_cast(th->obj); if (host == nullptr) { g_killed = true; LogDebug(LOG_HOST, "[FAIL] %s", threadName.c_str()); } if (g_killed) { delete th; return nullptr; } LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE Timer networkPeerStatusNotify(1000U, 5U); networkPeerStatusNotify.start(); StopWatch stopWatch; stopWatch.start(); while (!g_killed) { uint32_t ms = stopWatch.elapsed(); stopWatch.start(); host->m_adjSiteLoopMS = ms; if (host->m_dmr != nullptr) host->m_dmr->clockSiteData(ms); if (host->m_p25 != nullptr) host->m_p25->clockSiteData(ms); if (host->m_nxdn != nullptr) host->m_nxdn->clockSiteData(ms); if (host->m_allowStatusTransfer && host->m_network != nullptr) { networkPeerStatusNotify.clock(ms); if (networkPeerStatusNotify.isRunning() && networkPeerStatusNotify.hasExpired()) { networkPeerStatusNotify.start(); json::object statusObj = host->getStatus(); host->m_network->writePeerStatus(statusObj); } } if (host->m_state != STATE_IDLE) Thread::sleep(m_activeTickDelay); if (host->m_state == STATE_IDLE) Thread::sleep(m_idleTickDelay); } LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } return nullptr; } /* Entry point to presence update thread. */ void* Host::threadPresence(void* arg) { thread_t* th = (thread_t*)arg; if (th != nullptr) { #if defined(_WIN32) ::CloseHandle(th->thread); #else ::pthread_detach(th->thread); #endif // defined(_WIN32) std::string threadName("host:presence"); Host* host = static_cast(th->obj); if (host == nullptr) { g_killed = true; LogDebug(LOG_HOST, "[FAIL] %s", threadName.c_str()); } if (g_killed) { delete th; return nullptr; } LogDebug(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE Timer presenceNotifyTimer(1000U, host->m_presenceTime); presenceNotifyTimer.start(); bool hasInitialRegistered = false; StopWatch stopWatch; stopWatch.start(); while (!g_killed) { // scope is intentional { uint32_t ms = stopWatch.elapsed(); stopWatch.start(); presenceNotifyTimer.clock(ms); // VC -> CC presence registration if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr && host->m_RESTAPI != nullptr && !host->m_dmrCtrlChannel && !host->m_p25CtrlChannel && !host->m_nxdnCtrlChannel) { if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) { LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); hasInitialRegistered = true; std::string localAddress = network::udp::Socket::getLocalAddress(); if (host->m_restAddress == "0.0.0.0") { host->m_restAddress = localAddress; } // callback REST API to release the granted TG on the specified control channel json::object req = json::object(); req["channelNo"].set(host->m_channelNo); uint32_t peerId = host->m_network->getPeerId(); req["peerId"].set(peerId); req["restAddress"].set(host->m_restAddress); req["restPort"].set(host->m_restPort); int ret = RESTClient::send(host->m_controlChData.address(), host->m_controlChData.port(), host->m_controlChData.password(), HTTP_PUT, PUT_REGISTER_CC_VC, req, host->m_controlChData.ssl(), REST_QUICK_WAIT, false); if (ret != network::rest::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", host->m_controlChData.address().c_str(), host->m_controlChData.port()); } presenceNotifyTimer.start(); } } // CC -> FNE registered VC announcement if (host->m_dmrCtrlChannel || host->m_p25CtrlChannel || host->m_nxdnCtrlChannel) { if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || g_fireCCVCNotification) { g_fireCCVCNotification = false; if (host->m_network != nullptr && host->m_voiceChPeerId.size() > 0) { LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", host->m_network->getPeerId()); std::vector peers; for (auto it : host->m_voiceChPeerId) { peers.push_back(it.second); } host->m_network->announceSiteVCs(peers); } presenceNotifyTimer.start(); } } } if (host->m_state != STATE_IDLE) Thread::sleep(m_activeTickDelay); if (host->m_state == STATE_IDLE) Thread::sleep(m_idleTickDelay); } LogDebug(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } return nullptr; }