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;
parent
82a5b775a9
commit
629c2fe75e
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue