initial commit for dvmhost 3.55 dev branch; add support for std::function<> based lambdas as threads; split DMR, P25 and NXDN frame processing into their own source files for the host (Host.DMR.cpp, Host.P25.cpp, Host.NXDN.cpp) these files will contain host-level processing code for the specific protocols; implement protocol frame processors as their own running threads;

pull/42/head
Bryan Biedenkapp 2 years ago
parent 82a5b775a9
commit 629c2fe75e

@ -109,7 +109,7 @@ typedef unsigned long long ulong64_t;
#define __PROG_NAME__ "Digital Voice Modem (DVM) Host" #define __PROG_NAME__ "Digital Voice Modem (DVM) Host"
#define __NET_NAME__ "DVM_DMR_P25" #define __NET_NAME__ "DVM_DMR_P25"
#define __EXE_NAME__ "dvmhost" #define __EXE_NAME__ "dvmhost"
#define __VER__ "D03.50.00 (" __GIT_VER__ ")" #define __VER__ "D03.55.00 (" __GIT_VER__ ")"
#define __BUILD__ __DATE__ " " __TIME__ #define __BUILD__ __DATE__ " " __TIME__
#define HOST_SW_API #define HOST_SW_API

@ -12,6 +12,7 @@
// //
/* /*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -39,7 +40,8 @@
/// Initializes a new instance of the Thread class. /// Initializes a new instance of the Thread class.
/// </summary> /// </summary>
Thread::Thread() : Thread::Thread() :
m_thread() m_thread(),
m_started(false)
{ {
/* stub */ /* stub */
} }
@ -58,6 +60,10 @@ Thread::~Thread()
/// <returns>True, if thread started, otherwise false.</returns> /// <returns>True, if thread started, otherwise false.</returns>
bool Thread::run() bool Thread::run()
{ {
if (m_started)
return m_started;
m_started = true;
return ::pthread_create(&m_thread, NULL, helper, this) == 0; return ::pthread_create(&m_thread, NULL, helper, this) == 0;
} }
@ -90,7 +96,6 @@ void Thread::sleep(uint32_t ms)
void* Thread::helper(void* arg) void* Thread::helper(void* arg)
{ {
Thread* p = (Thread*)arg; Thread* p = (Thread*)arg;
p->entry(); p->entry();
return nullptr; return nullptr;

@ -12,6 +12,7 @@
// //
/* /*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -63,6 +64,10 @@ private:
/// <summary></summary> /// <summary></summary>
static void* helper(void* arg); static void* helper(void* arg);
public:
/// <summary>Flag indicating if the thread was started.</summary>
__READONLY_PROPERTY_PLAIN(bool, started, started);
}; };
#endif // __THREAD_H__ #endif // __THREAD_H__

@ -0,0 +1,64 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
//
/*
* 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(__THREAD_FUNC_H__)
#define __THREAD_FUNC_H__
#include "Defines.h"
#include "Thread.h"
#include <cassert>
#include <functional>
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a simple function threading mechanism.
// ---------------------------------------------------------------------------
class HOST_SW_API ThreadFunc : public Thread {
public:
/// <summary>Initializes a new instance of the ThreadFunc class.</summary>
ThreadFunc(std::function<void()>&& e) : Thread(),
m_entry(e)
{
assert(e != nullptr);
}
/// <summary>User-defined function to run for the thread main.</summary>
virtual void entry()
{
if (m_entry != nullptr)
m_entry();
}
private:
std::function<void()> m_entry;
};
#endif // __THREAD_FUNC_H__

@ -0,0 +1,314 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
//
/*
* Copyright (C) 2017-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.
*/
#include "Defines.h"
#include "host/Host.h"
using namespace modem;
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
// Macro to start DMR duplex idle transmission (or beacon)
#define START_DMR_DUPLEX_IDLE(x) \
if (control != nullptr) { \
if (m_duplex && !m_dmrTXTimer.isRunning()) { \
m_modem->writeDMRStart(x); \
m_dmrTXTimer.start(); \
} \
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to interrupt a running DMR beacon.
/// </summary>
/// <param name="control"></param>
void Host::interruptDMRBeacon(dmr::Control* control)
{
if (control != nullptr) {
if (m_dmrBeaconDurationTimer.isRunning() && !m_dmrBeaconDurationTimer.hasExpired()) {
if (m_dmrTSCCData && !m_dmrCtrlChannel) {
LogDebug(LOG_HOST, "interrupt DMR control, m_state = %u", m_state);
control->setCCHalted(true);
control->setCCRunning(false);
}
}
m_dmrBeaconDurationTimer.stop();
}
}
/// <summary>
/// Helper to read DMR slot 1 frames from modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterReadCallback"></param>
void Host::readFramesDMR1(dmr::Control* control, std::function<void()>&& afterReadCallback)
{
uint8_t data[dmr::DMR_FRAME_LENGTH_BYTES * 2U];
if (control != nullptr) {
// read DMR slot 1 frames from the modem, and if there is any
// write those frames to the DMR controller
uint32_t len = m_modem->readDMRFrame1(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// if the modem is in duplex -- process wakeup CSBKs
if (m_duplex) {
bool ret = control->processWakeup(data);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
}
else {
// in simplex directly process slot 1 frames
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
control->processFrame(1U, data, len);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
}
else if (m_state == STATE_DMR) {
// if the modem is in duplex, and hasn't started transmitting
// process wakeup CSBKs
if (m_duplex && !m_modem->hasTX()) {
bool ret = control->processWakeup(data);
if (ret) {
m_modem->writeDMRStart(true);
m_dmrTXTimer.start();
}
}
else {
// process slot 1 frames
bool ret = control->processFrame(1U, data, len);
if (ret) {
if (afterReadCallback != nullptr) {
afterReadCallback();
}
m_modeTimer.start();
if (m_duplex)
m_dmrTXTimer.start();
}
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "DMR modem data received, state = %u", m_state);
}
}
}
}
/// <summary>
/// Helper to write DMR slot 1 frames to modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterWriteCallback"></param>
void Host::writeFramesDMR1(dmr::Control* control, std::function<void()>&& afterWriteCallback)
{
uint8_t data[dmr::DMR_FRAME_LENGTH_BYTES * 2U];
if (control != nullptr) {
// check if there is space on the modem for DMR slot 1 frames,
// if there is read frames from the DMR controller and write it
// to the modem
bool ret = m_modem->hasDMRSpace1();
if (ret) {
uint32_t len = control->getFrame(1U, data);
if (len > 0U) {
// if the state is idle; set to DMR, start mode timer and start DMR idle frames
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
}
// if the state is DMR; start DMR idle frames and write DMR slot 1 data
if (m_state == STATE_DMR) {
START_DMR_DUPLEX_IDLE(true);
m_modem->writeDMRFrame1(data, len);
// if there is no DMR CC running; run the interrupt macro to stop
// any running DMR beacon
if (!control->getCCRunning()) {
interruptDMRBeacon(control);
}
if (afterWriteCallback != nullptr) {
afterWriteCallback();
}
m_modeTimer.start();
}
m_lastDstId = control->getLastDstId(1U);
m_lastSrcId = control->getLastSrcId(1U);
}
}
}
}
/// <summary>
/// Helper to read DMR slot 2 frames from modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterReadCallback"></param>
void Host::readFramesDMR2(dmr::Control* control, std::function<void()>&& afterReadCallback)
{
uint8_t data[dmr::DMR_FRAME_LENGTH_BYTES * 2U];
if (control != nullptr) {
// read DMR slot 2 frames from the modem, and if there is any
// write those frames to the DMR controller
uint32_t len = m_modem->readDMRFrame2(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// if the modem is in duplex -- process wakeup CSBKs
if (m_duplex) {
bool ret = control->processWakeup(data);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
}
else {
// in simplex -- directly process slot 2 frames
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
control->processFrame(2U, data, len);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
}
else if (m_state == STATE_DMR) {
// if the modem is in duplex, and hasn't started transmitting
// process wakeup CSBKs
if (m_duplex && !m_modem->hasTX()) {
bool ret = control->processWakeup(data);
if (ret) {
m_modem->writeDMRStart(true);
m_dmrTXTimer.start();
}
}
else {
// process slot 2 frames
bool ret = control->processFrame(2U, data, len);
if (ret) {
if (afterReadCallback != nullptr) {
afterReadCallback();
}
m_modeTimer.start();
if (m_duplex)
m_dmrTXTimer.start();
}
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "DMR modem data received, state = %u", m_state);
}
}
}
}
/// <summary>
/// Helper to write DMR slot 2 frames to modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterWriteCallback"></param>
void Host::writeFramesDMR2(dmr::Control* control, std::function<void()>&& afterWriteCallback)
{
uint8_t data[dmr::DMR_FRAME_LENGTH_BYTES * 2U];
if (control != nullptr) {
// check if there is space on the modem for DMR slot 2 frames,
// if there is read frames from the DMR controller and write it
// to the modem
bool ret = m_modem->hasDMRSpace2();
if (ret) {
uint32_t len = control->getFrame(2U, data);
if (len > 0U) {
// if the state is idle; set to DMR, start mode timer and start DMR idle frames
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
}
// if the state is DMR; start DMR idle frames and write DMR slot 2 data
if (m_state == STATE_DMR) {
START_DMR_DUPLEX_IDLE(true);
m_modem->writeDMRFrame2(data, len);
// if there is no DMR CC running; run the interrupt macro to stop
// any running DMR beacon
if (!control->getCCRunning()) {
interruptDMRBeacon(control);
}
if (afterWriteCallback != nullptr) {
afterWriteCallback();
}
m_modeTimer.start();
}
m_lastDstId = control->getLastDstId(2U);
m_lastSrcId = control->getLastSrcId(2U);
}
}
}
}

@ -0,0 +1,132 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
//
/*
* Copyright (C) 2017-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.
*/
#include "Defines.h"
#include "host/Host.h"
using namespace modem;
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to interrupt a running NXDN control channel.
/// </summary>
/// <param name="control"></param>
void Host::interruptNXDNControl(nxdn::Control* control)
{
if (control != nullptr) {
LogDebug(LOG_HOST, "interrupt NXDN control, m_state = %u", m_state);
control->setCCHalted(true);
if (m_nxdnBcastDurationTimer.isRunning() && !m_nxdnBcastDurationTimer.isPaused()) {
m_nxdnBcastDurationTimer.pause();
}
}
}
/// <summary>
/// Helper to read NXDN frames from modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterReadCallback"></param>
void Host::readFramesNXDN(nxdn::Control* control, std::function<void()>&& afterReadCallback)
{
uint8_t data[nxdn::NXDN_FRAME_LENGTH_BYTES * 2U];
if (control != nullptr) {
uint32_t len = m_modem->readNXDNFrame(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// process NXDN frames
bool ret = control->processFrame(data, len);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_NXDN);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
}
else if (m_state == STATE_NXDN) {
// process NXDN frames
bool ret = control->processFrame(data, len);
if (ret) {
m_modeTimer.start();
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "NXDN modem data received, state = %u", m_state);
}
}
}
}
/// <summary>
/// Helper to write NXDN frames to modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterWriteCallback"></param>
void Host::writeFramesNXDN(nxdn::Control* control, std::function<void()>&& afterWriteCallback)
{
uint8_t data[nxdn::NXDN_FRAME_LENGTH_BYTES * 2U];
// check if there is space on the modem for NXDN frames,
// if there is read frames from the NXDN controller and write it
// to the modem
if (control != nullptr) {
bool ret = m_modem->hasNXDNSpace();
if (ret) {
uint32_t len = control->getFrame(data);
if (len > 0U) {
// if the state is idle; set to NXDN and start mode timer
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_NXDN);
}
// if the state is NXDN; write NXDN data
if (m_state == STATE_NXDN) {
m_modem->writeNXDNFrame(data, len);
if (afterWriteCallback != nullptr) {
afterWriteCallback();
}
m_modeTimer.start();
}
m_lastDstId = control->getLastDstId();
m_lastSrcId = control->getLastSrcId();
}
}
}
}

@ -0,0 +1,220 @@
/**
* Digital Voice Modem - Host Software
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Host Software
*
*/
//
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
//
/*
* Copyright (C) 2017-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.
*/
#include "Defines.h"
#include "host/Host.h"
#include "HostMain.h"
using namespace modem;
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to interrupt a running P25 control channel.
/// </summary>
/// <param name="control"></param>
void Host::interruptP25Control(p25::Control* control)
{
if (control != nullptr) {
LogDebug(LOG_HOST, "interrupt P25 control, m_state = %u", m_state);
control->setCCHalted(true);
if (m_p25BcastDurationTimer.isRunning() && !m_p25BcastDurationTimer.isPaused()) {
m_p25BcastDurationTimer.pause();
}
}
}
/// <summary>
/// Helper to read P25 frames from modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterReadCallback"></param>
void Host::readFramesP25(p25::Control* control, std::function<void()>&& afterReadCallback)
{
uint8_t data[p25::P25_LDU_FRAME_LENGTH_BYTES * 2U];
// read P25 frames from modem, and if there are frames
// write those frames to the P25 controller
if (control != nullptr) {
uint32_t len = m_modem->readP25Frame(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// process P25 frames
bool ret = control->processFrame(data, len);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_P25);
if (afterReadCallback != nullptr) {
afterReadCallback();
}
}
else {
ret = control->writeRF_VoiceEnd();
if (ret) {
if (afterReadCallback != nullptr) {
afterReadCallback();
}
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_P25);
}
if (m_state == STATE_P25) {
m_modeTimer.start();
}
// if the modem is in duplex -- handle P25 CC burst control
if (m_duplex) {
if (m_p25BcastDurationTimer.isPaused() && !control->getCCHalted()) {
m_p25BcastDurationTimer.resume();
}
if (control->getCCHalted()) {
control->setCCHalted(false);
}
if (g_fireP25Control) {
m_modeTimer.stop();
}
}
else {
m_p25BcastDurationTimer.stop();
}
}
}
}
else if (m_state == STATE_P25) {
// process P25 frames
bool ret = control->processFrame(data, len);
if (ret) {
m_modeTimer.start();
}
else {
ret = control->writeRF_VoiceEnd();
if (ret) {
m_modeTimer.start();
}
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "P25 modem data received, state = %u", m_state);
}
}
}
}
/// <summary>
/// Helper to write P25 frames to modem.
/// </summary>
/// <param name="control"></param>
/// <param name="afterWriteCallback"></param>
void Host::writeFramesP25(p25::Control* control, std::function<void()>&& afterWriteCallback)
{
uint8_t data[p25::P25_LDU_FRAME_LENGTH_BYTES * 2U];
// check if there is space on the modem for P25 frames,
// if there is read frames from the P25 controller and write it
// to the modem
if (control != nullptr) {
uint8_t nextLen = control->peekFrameLength();
if (nextLen > 0U) {
bool ret = m_modem->hasP25Space(nextLen);
if (ret) {
uint32_t len = control->getFrame(data);
if (len > 0U) {
// if the state is idle; set to P25 and start mode timer
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_P25);
}
// if the state is P25; write P25 frame data
if (m_state == STATE_P25) {
m_modem->writeP25Frame(data, len);
if (afterWriteCallback != nullptr) {
afterWriteCallback();
}
m_modeTimer.start();
}
m_lastDstId = control->getLastDstId();
m_lastSrcId = control->getLastSrcId();
}
else {
nextLen = 0U;
}
}
}
if (nextLen == 0U) {
// if we have no P25 data, and we're either idle or P25 state, check if we
// need to be starting the CC running flag or writing end of voice call data
if (m_state == STATE_IDLE || m_state == STATE_P25) {
if (control->getCCHalted()) {
control->setCCHalted(false);
}
// write end of voice if necessary
bool ret = control->writeRF_VoiceEnd();
if (ret) {
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_P25);
}
if (m_state == STATE_P25) {
m_modeTimer.start();
}
}
}
}
// if the modem is in duplex -- handle P25 CC burst control
if (m_duplex) {
if (m_p25BcastDurationTimer.isPaused() && !control->getCCHalted()) {
m_p25BcastDurationTimer.resume();
}
if (control->getCCHalted()) {
control->setCCHalted(false);
}
if (g_fireP25Control) {
m_modeTimer.stop();
}
}
}
}

@ -30,11 +30,8 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ */
#include "Defines.h" #include "Defines.h"
#include "dmr/Control.h"
#include "dmr/DMRUtils.h" #include "dmr/DMRUtils.h"
#include "p25/Control.h"
#include "p25/P25Utils.h" #include "p25/P25Utils.h"
#include "nxdn/Control.h"
#include "modem/port/ModemNullPort.h" #include "modem/port/ModemNullPort.h"
#include "modem/port/UARTPort.h" #include "modem/port/UARTPort.h"
#include "modem/port/PseudoPTYPort.h" #include "modem/port/PseudoPTYPort.h"
@ -46,6 +43,7 @@
#include "Log.h" #include "Log.h"
#include "StopWatch.h" #include "StopWatch.h"
#include "Thread.h" #include "Thread.h"
#include "ThreadFunc.h"
#include "Utils.h" #include "Utils.h"
using namespace network; using namespace network;
@ -141,6 +139,9 @@ Host::Host(const std::string& confFile) :
m_nxdnQueueSizeBytes(1488U), // 31 frames m_nxdnQueueSizeBytes(1488U), // 31 frames
m_authoritative(true), m_authoritative(true),
m_supervisor(false), m_supervisor(false),
m_dmrBeaconDurationTimer(1000U),
m_p25BcastDurationTimer(1000U),
m_nxdnBcastDurationTimer(1000U),
m_activeTickDelay(5U), m_activeTickDelay(5U),
m_idleTickDelay(5U), m_idleTickDelay(5U),
m_RESTAPI(nullptr) m_RESTAPI(nullptr)
@ -358,7 +359,6 @@ int Host::run()
// initialize DMR // initialize DMR
Timer dmrBeaconIntervalTimer(1000U); Timer dmrBeaconIntervalTimer(1000U);
Timer dmrBeaconDurationTimer(1000U);
std::unique_ptr<dmr::Control> dmr = nullptr; std::unique_ptr<dmr::Control> dmr = nullptr;
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
@ -410,7 +410,7 @@ int Host::run()
LogInfo(" Roaming Beacon Interval: %us", dmrBeaconInterval); LogInfo(" Roaming Beacon Interval: %us", dmrBeaconInterval);
LogInfo(" Roaming Beacon Duration: %us", dmrBeaconDuration); LogInfo(" Roaming Beacon Duration: %us", dmrBeaconDuration);
dmrBeaconDurationTimer.setTimeout(dmrBeaconDuration); m_dmrBeaconDurationTimer.setTimeout(dmrBeaconDuration);
dmrBeaconIntervalTimer.setTimeout(dmrBeaconInterval); dmrBeaconIntervalTimer.setTimeout(dmrBeaconInterval);
dmrBeaconIntervalTimer.start(); dmrBeaconIntervalTimer.start();
@ -452,7 +452,6 @@ int Host::run()
// initialize P25 // initialize P25
Timer p25BcastIntervalTimer(1000U); Timer p25BcastIntervalTimer(1000U);
Timer p25BcastDurationTimer(1000U);
std::unique_ptr<p25::Control> p25 = nullptr; std::unique_ptr<p25::Control> p25 = nullptr;
#if defined(ENABLE_P25) #if defined(ENABLE_P25)
@ -492,7 +491,7 @@ int Host::run()
LogInfo(" Control Broadcast Duration: %us", p25ControlBcstDuration); LogInfo(" Control Broadcast Duration: %us", p25ControlBcstDuration);
} }
p25BcastDurationTimer.setTimeout(p25ControlBcstDuration); m_p25BcastDurationTimer.setTimeout(p25ControlBcstDuration);
p25BcastIntervalTimer.setTimeout(p25ControlBcstInterval); p25BcastIntervalTimer.setTimeout(p25ControlBcstInterval);
p25BcastIntervalTimer.start(); p25BcastIntervalTimer.start();
@ -524,7 +523,6 @@ int Host::run()
// initialize NXDN // initialize NXDN
Timer nxdnBcastIntervalTimer(1000U); Timer nxdnBcastIntervalTimer(1000U);
Timer nxdnBcastDurationTimer(1000U);
std::unique_ptr<nxdn::Control> nxdn = nullptr; std::unique_ptr<nxdn::Control> nxdn = nullptr;
#if defined(ENABLE_NXDN) #if defined(ENABLE_NXDN)
@ -557,7 +555,7 @@ int Host::run()
LogInfo(" Control Broadcast Duration: %us", nxdnControlBcstDuration); LogInfo(" Control Broadcast Duration: %us", nxdnControlBcstDuration);
} }
nxdnBcastDurationTimer.setTimeout(nxdnControlBcstDuration); m_nxdnBcastDurationTimer.setTimeout(nxdnControlBcstDuration);
nxdnBcastIntervalTimer.setTimeout(nxdnControlBcstInterval); nxdnBcastIntervalTimer.setTimeout(nxdnControlBcstInterval);
nxdnBcastIntervalTimer.start(); nxdnBcastIntervalTimer.start();
@ -758,39 +756,6 @@ int Host::run()
bool hasTxShutdown = false; bool hasTxShutdown = false;
// Macro to interrupt a running P25 control channel transmission
#define INTERRUPT_P25_CONTROL \
if (p25 != nullptr) { \
LogDebug(LOG_HOST, "interrupt P25 control, m_state = %u", m_state); \
p25->setCCHalted(true); \
if (p25BcastDurationTimer.isRunning() && !p25BcastDurationTimer.isPaused()) { \
p25BcastDurationTimer.pause(); \
} \
}
// Macro to interrupt a running DMR roaming beacon
#define INTERRUPT_DMR_BEACON \
if (dmr != nullptr) { \
if (dmrBeaconDurationTimer.isRunning() && !dmrBeaconDurationTimer.hasExpired()) { \
if (m_dmrTSCCData && !m_dmrCtrlChannel) { \
LogDebug(LOG_HOST, "interrupt DMR control, m_state = %u", m_state); \
dmr->setCCHalted(true); \
dmr->setCCRunning(false); \
} \
} \
dmrBeaconDurationTimer.stop(); \
}
// Macro to interrupt a running NXDN control channel transmission
#define INTERRUPT_NXDN_CONTROL \
if (nxdn != nullptr) { \
LogDebug(LOG_HOST, "interrupt NXDN control, m_state = %u", m_state); \
nxdn->setCCHalted(true); \
if (nxdnBcastDurationTimer.isRunning() && !nxdnBcastDurationTimer.isPaused()) { \
nxdnBcastDurationTimer.pause(); \
} \
}
// Macro to start DMR duplex idle transmission (or beacon) // Macro to start DMR duplex idle transmission (or beacon)
#define START_DMR_DUPLEX_IDLE(x) \ #define START_DMR_DUPLEX_IDLE(x) \
if (dmr != nullptr) { \ if (dmr != nullptr) { \
@ -800,519 +765,256 @@ int Host::run()
} \ } \
} }
// main execution loop // setup protocol processor threads
while (!killed) { /** Digital Mobile Radio */
if (m_modem->hasLockout() && m_state != HOST_STATE_LOCKOUT) ThreadFunc dmrProcessThread([&, this]() {
setState(HOST_STATE_LOCKOUT); #if defined(ENABLE_DMR)
else if (!m_modem->hasLockout() && m_state == HOST_STATE_LOCKOUT) if (dmr != nullptr) {
setState(STATE_IDLE); LogDebug(LOG_HOST, "DMR, started frame processor");
while (!g_killed) {
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);
uint8_t data[220U];
uint32_t len;
bool ret;
if (!m_fixedMode) {
if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) {
setState(STATE_IDLE);
}
}
else {
m_modeTimer.stop();
if (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 (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 (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);
}
}
// ------------------------------------------------------ // ------------------------------------------------------
// -- Write to Modem Processing -- // -- Write to Modem Processing --
// ------------------------------------------------------ // ------------------------------------------------------
/** Digital Mobile Radio */ // write DMR slot 1 frames to modem
#if defined(ENABLE_DMR) writeFramesDMR1(dmr.get(), [&, this]() {
if (dmr != nullptr) { // if there is a P25 CC running; halt the CC
// check if there is space on the modem for DMR slot 1 frames, if (p25 != nullptr) {
// if there is read frames from the DMR controller and write it if (p25->getCCRunning() && !p25->getCCHalted()) {
// to the modem this->interruptP25Control(p25.get());
ret = m_modem->hasDMRSpace1(); }
if (ret) {
len = dmr->getFrame(1U, data);
if (len > 0U) {
// if the state is idle; set to DMR, start mode timer and start DMR idle frames
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
} }
// if the state is DMR; start DMR idle frames and write DMR slot 1 data // if there is a NXDN CC running; halt the CC
if (m_state == STATE_DMR) { if (nxdn != nullptr) {
START_DMR_DUPLEX_IDLE(true); if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
this->interruptNXDNControl(nxdn.get());
m_modem->writeDMRFrame1(data, len);
// if there is no DMR CC running; run the interrupt macro to stop
// any running DMR beacon
if (!dmr->getCCRunning()) {
INTERRUPT_DMR_BEACON;
} }
}
});
// write DMR slot 2 frames to modem
writeFramesDMR2(dmr.get(), [&, this]() {
// if there is a P25 CC running; halt the CC // if there is a P25 CC running; halt the CC
if (p25 != nullptr) { if (p25 != nullptr) {
if (p25->getCCRunning() && !p25->getCCHalted()) { if (p25->getCCRunning() && !p25->getCCHalted()) {
INTERRUPT_P25_CONTROL; this->interruptP25Control(p25.get());
} }
} }
// if there is a NXDN CC running; halt the CC // if there is a NXDN CC running; halt the CC
if (nxdn != nullptr) { if (nxdn != nullptr) {
if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
INTERRUPT_NXDN_CONTROL; this->interruptNXDNControl(nxdn.get());
} }
} }
});
m_modeTimer.start(); // ------------------------------------------------------
// -- Read from Modem Processing --
// ------------------------------------------------------
// read DMR slot 1 frames from modem
readFramesDMR1(dmr.get(), [&, this]() {
if (dmr != nullptr) {
this->interruptDMRBeacon(dmr.get());
} }
m_lastDstId = dmr->getLastDstId(1U); // if there is a P25 CC running; halt the CC
m_lastSrcId = dmr->getLastSrcId(1U); if (p25 != nullptr) {
if (p25->getCCRunning() && !p25->getCCHalted()) {
this->interruptP25Control(p25.get());
} }
} }
// check if there is space on the modem for DMR slot 2 frames, // if there is a NXDN CC running; halt the CC
// if there is read frames from the DMR controller and write it if (nxdn != nullptr) {
// to the modem if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
ret = m_modem->hasDMRSpace2(); this->interruptNXDNControl(nxdn.get());
if (ret) {
len = dmr->getFrame(2U, data);
if (len > 0U) {
// if the state is idle; set to DMR, start mode timer and start DMR idle frames
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
} }
}
});
// if the state is DMR; start DMR idle frames and write DMR slot 2 data // read DMR slot 2 frames from modem
if (m_state == STATE_DMR) { readFramesDMR2(dmr.get(), [&, this]() {
START_DMR_DUPLEX_IDLE(true); if (dmr != nullptr) {
this->interruptDMRBeacon(dmr.get());
m_modem->writeDMRFrame2(data, len);
// if there is no DMR CC running; run the interrupt macro to stop
// any running DMR beacon
if (!dmr->getCCRunning()) {
INTERRUPT_DMR_BEACON;
} }
// if there is a P25 CC running; halt the CC // if there is a P25 CC running; halt the CC
if (p25 != nullptr) { if (p25 != nullptr) {
if (p25->getCCRunning() && !p25->getCCHalted()) { if (p25->getCCRunning() && !p25->getCCHalted()) {
INTERRUPT_P25_CONTROL; this->interruptP25Control(p25.get());
} }
} }
// if there is a NXDN CC running; halt the CC // if there is a NXDN CC running; halt the CC
if (nxdn != nullptr) { if (nxdn != nullptr) {
if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
INTERRUPT_NXDN_CONTROL; this->interruptNXDNControl(nxdn.get());
} }
} }
});
m_modeTimer.start(); if (m_state != STATE_IDLE)
} Thread::sleep(m_activeTickDelay);
if (m_state == STATE_IDLE)
m_lastDstId = dmr->getLastDstId(2U); Thread::sleep(m_idleTickDelay);
m_lastSrcId = dmr->getLastSrcId(2U);
}
} }
} }
#endif // defined(ENABLE_DMR) #endif // defined(ENABLE_DMR)
});
dmrProcessThread.run();
/** Project 25 */ /** Project 25 */
ThreadFunc p25ProcessThread([&, this]() {
#if defined(ENABLE_P25) #if defined(ENABLE_P25)
// check if there is space on the modem for P25 frames,
// if there is read frames from the P25 controller and write it
// to the modem
if (p25 != nullptr) { if (p25 != nullptr) {
uint8_t nextLen = p25->peekFrameLength(); LogDebug(LOG_HOST, "P25, started frame processor");
if (nextLen > 0U) { while (!g_killed) {
ret = m_modem->hasP25Space(nextLen); // ------------------------------------------------------
if (ret) { // -- Write to Modem Processing --
len = p25->getFrame(data); // ------------------------------------------------------
if (len > 0U) {
// if the state is idle; set to P25 and start mode timer
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_P25);
}
// if the state is P25; write P25 frame data
if (m_state == STATE_P25) {
m_modem->writeP25Frame(data, len);
INTERRUPT_DMR_BEACON; // write P25 frames to modem
writeFramesP25(p25.get(), [&, this]() {
if (dmr != nullptr) {
this->interruptDMRBeacon(dmr.get());
}
// if there is a NXDN CC running; halt the CC // if there is a NXDN CC running; halt the CC
if (nxdn != nullptr) { if (nxdn != nullptr) {
if (nxdn->getCCRunning() && !nxdn->getCCHalted()) { if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
INTERRUPT_NXDN_CONTROL; this->interruptNXDNControl(nxdn.get());
}
}
m_modeTimer.start();
}
m_lastDstId = p25->getLastDstId();
m_lastSrcId = p25->getLastSrcId();
}
else {
nextLen = 0U;
}
} }
} }
});
if (nextLen == 0U) { // ------------------------------------------------------
// if we have no P25 data, and we're either idle or P25 state, check if we // -- Read from Modem Processing --
// need to be starting the CC running flag or writing end of voice call data // ------------------------------------------------------
if (m_state == STATE_IDLE || m_state == STATE_P25) {
if (p25->getCCHalted()) {
p25->setCCHalted(false);
}
// write end of voice if necessary
ret = p25->writeRF_VoiceEnd();
if (ret) {
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_P25);
}
if (m_state == STATE_P25) { // read P25 frames from modem
m_modeTimer.start(); readFramesP25(p25.get(), [&, this]() {
} if (dmr != nullptr) {
} this->interruptDMRBeacon(dmr.get());
}
} }
// if the modem is in duplex -- handle P25 CC burst control // if there is a NXDN CC running; halt the CC
if (m_duplex) { if (nxdn != nullptr) {
if (p25BcastDurationTimer.isPaused() && !p25->getCCHalted()) { if (nxdn->getCCRunning() && !nxdn->getCCHalted()) {
p25BcastDurationTimer.resume(); this->interruptNXDNControl(nxdn.get());
} }
if (p25->getCCHalted()) {
p25->setCCHalted(false);
} }
});
if (g_fireP25Control) { if (m_state != STATE_IDLE)
m_modeTimer.stop(); Thread::sleep(m_activeTickDelay);
} if (m_state == STATE_IDLE)
Thread::sleep(m_idleTickDelay);
} }
} }
#endif // defined(ENABLE_P25) #endif // defined(ENABLE_P25)
});
p25ProcessThread.run();
/** Next Generation Digital Narrowband */ /** Next Generation Digital Narrowband */
ThreadFunc nxdnProcessThread([&, this]() {
#if defined(ENABLE_NXDN) #if defined(ENABLE_NXDN)
// check if there is space on the modem for NXDN frames,
// if there is read frames from the NXDN controller and write it
// to the modem
if (nxdn != nullptr) { if (nxdn != nullptr) {
ret = m_modem->hasNXDNSpace(); LogDebug(LOG_HOST, "NXDN, started frame processor");
if (ret) { while (!g_killed) {
len = nxdn->getFrame(data); // ------------------------------------------------------
if (len > 0U) { // -- Write to Modem Processing --
// if the state is idle; set to NXDN and start mode timer // ------------------------------------------------------
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_netModeHang);
setState(STATE_NXDN);
}
// if the state is NXDN; write NXDN data
if (m_state == STATE_NXDN) {
m_modem->writeNXDNFrame(data, len);
INTERRUPT_DMR_BEACON; // write NXDN frames to modem
writeFramesNXDN(nxdn.get(), [&, this]() {
if (dmr != nullptr) {
this->interruptDMRBeacon(dmr.get());
}
// if there is a P25 CC running; halt the CC // if there is a P25 CC running; halt the CC
if (p25 != nullptr) { if (p25 != nullptr) {
if (p25->getCCRunning() && !p25->getCCHalted()) { if (p25->getCCRunning() && !p25->getCCHalted()) {
INTERRUPT_P25_CONTROL; this->interruptP25Control(p25.get());
} }
} }
});
m_modeTimer.start();
}
m_lastDstId = nxdn->getLastDstId();
m_lastSrcId = nxdn->getLastSrcId();
}
}
}
#endif // defined(ENABLE_NXDN)
// ------------------------------------------------------
// -- Modem Clocking --
// ------------------------------------------------------
ms = stopWatch.elapsed();
stopWatch.start();
m_modem->clock(ms);
// ------------------------------------------------------ // ------------------------------------------------------
// -- Read from Modem Processing -- // -- Read from Modem Processing --
// ------------------------------------------------------ // ------------------------------------------------------
/** Digital Mobile Radio */ // read NXDN frames from modem
#if defined(ENABLE_DMR) readFramesNXDN(nxdn.get(), [&, this]() {
if (dmr != nullptr) { if (dmr != nullptr) {
// read DMR slot 1 frames from the modem, and if there is any this->interruptDMRBeacon(dmr.get());
// write those frames to the DMR controller
len = m_modem->readDMRFrame1(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// if the modem is in duplex -- process wakeup CSBKs
if (m_duplex) {
bool ret = dmr->processWakeup(data);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
INTERRUPT_DMR_BEACON;
INTERRUPT_P25_CONTROL;
INTERRUPT_NXDN_CONTROL;
} }
}
else {
// in simplex directly process slot 1 frames
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
dmr->processFrame(1U, data, len);
INTERRUPT_DMR_BEACON; // if there is a P25 CC running; halt the CC
INTERRUPT_P25_CONTROL; if (p25 != nullptr) {
INTERRUPT_NXDN_CONTROL; if (p25->getCCRunning() && !p25->getCCHalted()) {
} this->interruptP25Control(p25.get());
}
else if (m_state == STATE_DMR) {
// if the modem is in duplex, and hasn't started transmitting
// process wakeup CSBKs
if (m_duplex && !m_modem->hasTX()) {
bool ret = dmr->processWakeup(data);
if (ret) {
m_modem->writeDMRStart(true);
m_dmrTXTimer.start();
} }
} }
else { });
// process slot 1 frames
bool ret = dmr->processFrame(1U, data, len);
if (ret) {
INTERRUPT_DMR_BEACON;
INTERRUPT_P25_CONTROL;
INTERRUPT_NXDN_CONTROL;
m_modeTimer.start(); if (m_state != STATE_IDLE)
if (m_duplex) Thread::sleep(m_activeTickDelay);
m_dmrTXTimer.start(); if (m_state == STATE_IDLE)
} Thread::sleep(m_idleTickDelay);
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "DMR modem data received, state = %u", m_state);
} }
} }
#endif // defined(ENABLE_NXDN)
});
nxdnProcessThread.run();
// read DMR slot 2 frames from the modem, and if there is any // main execution loop
// write those frames to the DMR controller while (!killed) {
len = m_modem->readDMRFrame2(data); if (m_modem->hasLockout() && m_state != HOST_STATE_LOCKOUT)
if (len > 0U) { setState(HOST_STATE_LOCKOUT);
if (m_state == STATE_IDLE) { else if (!m_modem->hasLockout() && m_state == HOST_STATE_LOCKOUT)
// if the modem is in duplex -- process wakeup CSBKs setState(STATE_IDLE);
if (m_duplex) {
bool ret = dmr->processWakeup(data);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
INTERRUPT_DMR_BEACON; if (m_modem->hasError() && m_state != HOST_STATE_ERROR)
INTERRUPT_P25_CONTROL; setState(HOST_STATE_ERROR);
INTERRUPT_NXDN_CONTROL; else if (!m_modem->hasError() && m_state == HOST_STATE_ERROR)
} setState(STATE_IDLE);
}
else {
// in simplex -- directly process slot 2 frames
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_DMR);
START_DMR_DUPLEX_IDLE(true);
dmr->processFrame(2U, data, len); uint32_t ms = stopWatch.elapsed();
if (ms > 1U)
m_modem->clock(ms);
INTERRUPT_DMR_BEACON; if (!m_fixedMode) {
INTERRUPT_P25_CONTROL; if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) {
INTERRUPT_NXDN_CONTROL; setState(STATE_IDLE);
}
}
else if (m_state == STATE_DMR) {
// if the modem is in duplex, and hasn't started transmitting
// process wakeup CSBKs
if (m_duplex && !m_modem->hasTX()) {
bool ret = dmr->processWakeup(data);
if (ret) {
m_modem->writeDMRStart(true);
m_dmrTXTimer.start();
} }
} }
else { else {
// process slot 2 frames m_modeTimer.stop();
bool ret = dmr->processFrame(2U, data, len); if (dmr != nullptr && m_state != STATE_DMR && !m_modem->hasTX()) {
if (ret) { LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_DMR);
INTERRUPT_DMR_BEACON; setState(STATE_DMR);
INTERRUPT_P25_CONTROL;
INTERRUPT_NXDN_CONTROL;
m_modeTimer.start();
if (m_duplex)
m_dmrTXTimer.start();
}
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "DMR modem data received, state = %u", m_state);
}
}
}
#endif // defined(ENABLE_DMR)
/** Project 25 */
#if defined(ENABLE_P25)
// read P25 frames from modem, and if there are frames
// write those frames to the P25 controller
if (p25 != nullptr) {
len = m_modem->readP25Frame(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// process P25 frames
bool ret = p25->processFrame(data, len);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_P25);
INTERRUPT_DMR_BEACON;
//INTERRUPT_P25_CONTROL;
INTERRUPT_NXDN_CONTROL;
} }
else { if (p25 != nullptr && m_state != STATE_P25 && !m_modem->hasTX()) {
ret = p25->writeRF_VoiceEnd(); LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_P25);
if (ret) {
INTERRUPT_DMR_BEACON;
INTERRUPT_NXDN_CONTROL;
if (m_state == STATE_IDLE) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_P25); setState(STATE_P25);
} }
if (nxdn != nullptr && m_state != STATE_NXDN && !m_modem->hasTX()) {
if (m_state == STATE_P25) { LogDebug(LOG_HOST, "fixed mode state abnormal, m_state = %u, state = %u", m_state, STATE_NXDN);
m_modeTimer.start(); setState(STATE_NXDN);
}
// if the modem is in duplex -- handle P25 CC burst control
if (m_duplex) {
if (p25BcastDurationTimer.isPaused() && !p25->getCCHalted()) {
p25BcastDurationTimer.resume();
} }
if (p25->getCCHalted()) {
p25->setCCHalted(false);
} }
if (g_fireP25Control) { // ------------------------------------------------------
m_modeTimer.stop(); // -- Modem Clocking --
} // ------------------------------------------------------
}
else {
p25BcastDurationTimer.stop();
}
}
}
}
else if (m_state == STATE_P25) {
// process P25 frames
bool ret = p25->processFrame(data, len);
if (ret) {
m_modeTimer.start();
//INTERRUPT_P25_CONTROL;
}
else {
ret = p25->writeRF_VoiceEnd();
if (ret) {
m_modeTimer.start();
}
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "P25 modem data received, state = %u", m_state);
}
}
}
#endif // defined(ENABLE_P25)
/** Next Generation Digital Narrowband */ ms = stopWatch.elapsed();
// read NXDN frames from modem, and if there are frames stopWatch.start();
// write those frames to the NXDN controller
#if defined(ENABLE_NXDN)
if (nxdn != nullptr) {
len = m_modem->readNXDNFrame(data);
if (len > 0U) {
if (m_state == STATE_IDLE) {
// process NXDN frames
bool ret = nxdn->processFrame(data, len);
if (ret) {
m_modeTimer.setTimeout(m_rfModeHang);
setState(STATE_NXDN);
INTERRUPT_DMR_BEACON; m_modem->clock(ms);
INTERRUPT_P25_CONTROL;
}
}
else if (m_state == STATE_NXDN) {
// process NXDN frames
bool ret = nxdn->processFrame(data, len);
if (ret) {
m_modeTimer.start();
//INTERRUPT_NXDN_CONTROL;
}
}
else if (m_state != HOST_STATE_LOCKOUT) {
LogWarning(LOG_HOST, "NXDN modem data received, state = %u", m_state);
}
}
}
#endif // defined(ENABLE_NXDN)
// ------------------------------------------------------ // ------------------------------------------------------
// -- Network, DMR, and P25 Clocking -- // -- Network, DMR, and P25 Clocking --
@ -1342,11 +1044,11 @@ int Host::run()
m_cwIdTimer.clock(ms); m_cwIdTimer.clock(ms);
if (m_cwIdTimer.isRunning() && m_cwIdTimer.hasExpired()) { if (m_cwIdTimer.isRunning() && m_cwIdTimer.hasExpired()) {
if (!m_modem->hasTX() && !m_p25CtrlChannel && !m_dmrCtrlChannel) { if (!m_modem->hasTX() && !m_p25CtrlChannel && !m_dmrCtrlChannel) {
if (dmrBeaconDurationTimer.isRunning() || p25BcastDurationTimer.isRunning()) { if (m_dmrBeaconDurationTimer.isRunning() || m_p25BcastDurationTimer.isRunning()) {
LogDebug(LOG_HOST, "CW, beacon or CC timer running, ceasing"); LogDebug(LOG_HOST, "CW, beacon or CC timer running, ceasing");
dmrBeaconDurationTimer.stop(); m_dmrBeaconDurationTimer.stop();
p25BcastDurationTimer.stop(); m_p25BcastDurationTimer.stop();
} }
LogDebug(LOG_HOST, "CW, start transmitting"); LogDebug(LOG_HOST, "CW, start transmitting");
@ -1424,14 +1126,14 @@ int Host::run()
LogDebug(LOG_HOST, "DMR, roaming beacon burst"); LogDebug(LOG_HOST, "DMR, roaming beacon burst");
} }
dmrBeaconIntervalTimer.start(); dmrBeaconIntervalTimer.start();
dmrBeaconDurationTimer.start(); m_dmrBeaconDurationTimer.start();
} }
} }
// clock and check DMR roaming beacon duration timer // clock and check DMR roaming beacon duration timer
dmrBeaconDurationTimer.clock(ms); m_dmrBeaconDurationTimer.clock(ms);
if (dmrBeaconDurationTimer.isRunning() && dmrBeaconDurationTimer.hasExpired()) { if (m_dmrBeaconDurationTimer.isRunning() && m_dmrBeaconDurationTimer.hasExpired()) {
dmrBeaconDurationTimer.stop(); m_dmrBeaconDurationTimer.stop();
if (!m_fixedMode) { if (!m_fixedMode) {
if (m_state == STATE_DMR && !m_modeTimer.isRunning()) { if (m_state == STATE_DMR && !m_modeTimer.isRunning()) {
@ -1481,23 +1183,23 @@ int Host::run()
g_fireP25Control = false; g_fireP25Control = false;
p25BcastIntervalTimer.start(); p25BcastIntervalTimer.start();
p25BcastDurationTimer.start(); m_p25BcastDurationTimer.start();
// if the CC is continuous -- clock one cycle into the duration timer // if the CC is continuous -- clock one cycle into the duration timer
if (m_p25CtrlChannel) { if (m_p25CtrlChannel) {
p25BcastDurationTimer.clock(ms); m_p25BcastDurationTimer.clock(ms);
} }
} }
} }
if (p25BcastDurationTimer.isPaused()) { if (m_p25BcastDurationTimer.isPaused()) {
p25BcastDurationTimer.resume(); m_p25BcastDurationTimer.resume();
} }
// clock and check P25 CC broadcast duration timer // clock and check P25 CC broadcast duration timer
p25BcastDurationTimer.clock(ms); m_p25BcastDurationTimer.clock(ms);
if (p25BcastDurationTimer.isRunning() && p25BcastDurationTimer.hasExpired()) { if (m_p25BcastDurationTimer.isRunning() && m_p25BcastDurationTimer.hasExpired()) {
p25BcastDurationTimer.stop(); m_p25BcastDurationTimer.stop();
p25->setCCRunning(false); p25->setCCRunning(false);
@ -1548,23 +1250,23 @@ int Host::run()
g_fireNXDNControl = false; g_fireNXDNControl = false;
nxdnBcastIntervalTimer.start(); nxdnBcastIntervalTimer.start();
nxdnBcastDurationTimer.start(); m_nxdnBcastDurationTimer.start();
// if the CC is continuous -- clock one cycle into the duration timer // if the CC is continuous -- clock one cycle into the duration timer
if (m_nxdnCtrlChannel) { if (m_nxdnCtrlChannel) {
nxdnBcastDurationTimer.clock(ms); m_nxdnBcastDurationTimer.clock(ms);
} }
} }
} }
if (nxdnBcastDurationTimer.isPaused()) { if (m_nxdnBcastDurationTimer.isPaused()) {
nxdnBcastDurationTimer.resume(); m_nxdnBcastDurationTimer.resume();
} }
// clock and check NXDN CC broadcast duration timer // clock and check NXDN CC broadcast duration timer
nxdnBcastDurationTimer.clock(ms); m_nxdnBcastDurationTimer.clock(ms);
if (nxdnBcastDurationTimer.isRunning() && nxdnBcastDurationTimer.hasExpired()) { if (m_nxdnBcastDurationTimer.isRunning() && m_nxdnBcastDurationTimer.hasExpired()) {
nxdnBcastDurationTimer.stop(); m_nxdnBcastDurationTimer.stop();
nxdn->setCCRunning(false); nxdn->setCCRunning(false);
@ -1589,6 +1291,11 @@ int Host::run()
#endif // defined(ENABLE_NXDN) #endif // defined(ENABLE_NXDN)
if (g_killed) { if (g_killed) {
// shutdown reader threads
dmrProcessThread.wait();
p25ProcessThread.wait();
nxdnProcessThread.wait();
#if defined(ENABLE_DMR) #if defined(ENABLE_DMR)
if (dmr != nullptr) { if (dmr != nullptr) {
if (m_dmrCtrlChannel) { if (m_dmrCtrlChannel) {
@ -1600,7 +1307,7 @@ int Host::run()
dmr->setCCRunning(false); dmr->setCCRunning(false);
dmr->setCCHalted(true); dmr->setCCHalted(true);
dmrBeaconDurationTimer.stop(); m_dmrBeaconDurationTimer.stop();
dmrBeaconIntervalTimer.stop(); dmrBeaconIntervalTimer.stop();
} }
} }
@ -1616,7 +1323,7 @@ int Host::run()
p25->setCCRunning(false); p25->setCCRunning(false);
p25BcastDurationTimer.stop(); m_p25BcastDurationTimer.stop();
p25BcastIntervalTimer.stop(); p25BcastIntervalTimer.stop();
} }
} }
@ -1632,7 +1339,7 @@ int Host::run()
nxdn->setCCRunning(false); nxdn->setCCRunning(false);
nxdnBcastDurationTimer.stop(); m_nxdnBcastDurationTimer.stop();
nxdnBcastIntervalTimer.stop(); nxdnBcastIntervalTimer.stop();
} }
} }

@ -32,6 +32,9 @@
#define __HOST_H__ #define __HOST_H__
#include "Defines.h" #include "Defines.h"
#include "dmr/Control.h"
#include "p25/Control.h"
#include "nxdn/Control.h"
#include "network/Network.h" #include "network/Network.h"
#include "network/RESTAPI.h" #include "network/RESTAPI.h"
#include "modem/Modem.h" #include "modem/Modem.h"
@ -44,6 +47,7 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <functional>
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Class Prototypes // Class Prototypes
@ -151,6 +155,10 @@ private:
bool m_authoritative; bool m_authoritative;
bool m_supervisor; bool m_supervisor;
Timer m_dmrBeaconDurationTimer;
Timer m_p25BcastDurationTimer;
Timer m_nxdnBcastDurationTimer;
uint8_t m_activeTickDelay; uint8_t m_activeTickDelay;
uint8_t m_idleTickDelay; uint8_t m_idleTickDelay;
@ -178,6 +186,37 @@ private:
void createLockFile(const char* state) const; void createLockFile(const char* state) const;
/// <summary>Helper to remove the state lock file.</summary> /// <summary>Helper to remove the state lock file.</summary>
void removeLockFile() const; void removeLockFile() const;
/** Digital Mobile Radio */
/// <summary>Helper to interrupt a running DMR beacon.</summary>
void interruptDMRBeacon(dmr::Control* control);
/// <summary>Helper to read DMR slot 1 frames from modem.</summary>
void readFramesDMR1(dmr::Control* control, std::function<void()>&& afterReadCallback);
/// <summary>Helper to write DMR slot 1 frames to modem.</summary>
void writeFramesDMR1(dmr::Control* control, std::function<void()>&& afterWriteCallback);
/// <summary>Helper to read DMR slot 2 frames from modem.</summary>
void readFramesDMR2(dmr::Control* control, std::function<void()>&& afterReadCallback);
/// <summary>Helper to write DMR slot 2 frames to modem.</summary>
void writeFramesDMR2(dmr::Control* control, std::function<void()>&& afterWriteCallback);
/** Project 25 */
/// <summary>Helper to interrupt a running P25 control channel.</summary>
void interruptP25Control(p25::Control* control);
/// <summary>Helper to read P25 frames from modem.</summary>
void readFramesP25(p25::Control* control, std::function<void()>&& afterReadCallback);
/// <summary>Helper to write P25 frames to modem.</summary>
void writeFramesP25(p25::Control* control, std::function<void()>&& afterWriteCallback);
/** Next Generation Digital Narrowband */
/// <summary>Helper to interrupt a running NXDN control channel.</summary>
void interruptNXDNControl(nxdn::Control* control);
/// <summary>Helper to read NXDN frames from modem.</summary>
void readFramesNXDN(nxdn::Control* control, std::function<void()>&& afterReadCallback);
/// <summary>Helper to write NXDN frames to modem.</summary>
void writeFramesNXDN(nxdn::Control* control, std::function<void()>&& afterWriteCallback);
}; };
#endif // __HOST_H__ #endif // __HOST_H__

Loading…
Cancel
Save

Powered by TurnKey Linux.