R04J32 Merge to Master (#95)
* implement user-defined adj site mapping to allow fine grained control over neighboring site announcements; * BUGFIX: patch issue where the FNE would not maintain the source peer ID for certain routing scenarios; * fix typo in original implementation, peerId should be the peerId and ssrc should become the originating peer ID; remove peer check throwing a warning; * log ssrc for various data points (the ssrc is the RTP originating sync source which would be the peer ID of the origination of a RTP packet); * bump build number; * add peer ID masking support (this is useful for FNEs that are "public" links where internal peer IDs don't need to be retained); more work for maintaining the originating stream sync source; refactor how the FNE handles peer network connections; * throw a warning if the user configured more then 8 upstream peer connections (more than this number can cause performance problems); * make non-peer-link peer ID masking optional; * add checking for traffic repeat to verify we are not trying to send traffic back to the source; * add checking for traffic repeat to verify we are not trying to send traffic back to an external source; * convert peer network protocol packet processing to a threaded model; * update copyright dates; * implement REST API for manipulating adjacent site map entries; * fix missed REST API initializer, need to pass the adj site map; * add DFSI/V.24 full duplex option (this allows dvmhost to repeat incoming frames back out); * Fix incorrect check of DFSI and FSC operating modes that resulted in a segfault (#92) Co-authored-by: faulty <faulty@evilcomputing.net> * document p25CorrCount in depth more; set the default p25CorrCount to 2; * properly call PacketBuffers clear() before leaving a scope to ensure contained buffers are deleted (otherwise we'll leak memory); * skip the first 6 bits of the TIA-102 DFSI VHDR (this is very strange, and is probably not kosher); * transparently pass voice frames with FID $20 (assumed to be Kenwood) when the PF flag is set (this allows Kenwoods flavor of encryption to pass); * relabel FID_DMRA to FID_MOT (this feature ID is assigned to Motorola); handle more conditions for FID $20 (Kenwood) on voice frames; * remove filterHeaders (this is deprecated, HDUs aren't sent over the network); perform frame length validation for P25 network frames (unlike DMR and NXDN P25 frames are variable length, requring length validation to prevent buffer under or overflows); fix issue with addr variable not being freed in InfluxDB handler; * [EXPERIMENTAL] add very initial support for dvmpatch to talk to a MMDVM P25 Gateway in P25 mode; * fixup dvmpatch support for talking to MMDVM P25 Gateway; * fix up handling of MMDVM call termination; * more work on better signalling end of call for MMDVM P25 gateway patches; better log traffic from MMDVM; * correct some incorrect timing; * add support for properly authorizing a peer to send Inhibit/Uninhibit commands; add some NXDN constants for remote control; * add columns to main peered peer list to represent if a peer is allowed to inhibit; * fixup informational logging; * add auto generation of a peer password; slightly increase the dialog size of the peer edit window; * fix TIA VHDR incorrect length; correct gatekeep accidentally setting TIA StartOfStream to 4 bytes instead of 3, because you know the TIA spec is incomprehensible with its bit alignment; * add some permanent log trace for DFSI over UDP tracing; correct startOfStreamTIA incorrectly sending a LDU1 NID; correct some offsets; * make sure we send out heartbeats *before* the heartbeat time; * send Start of Stream with voice data; * free memory in error case; remove unused variables; * correct some buffer allocations; ensure the AES class is deleted after use; * implement support for legacy radio affiliation (like P25) for DMR and NXDN (this fixes an issue where conventional DMR and NXDN systems would be unable to transmit onto affiliation only TGs); correct bad CSBK decoding in the FNE (forgot to check for dataSync); * code cleanup to correct compiler warnings; * add string file meta data info to Win32 EXEs; add icons to Win32 EXEs; * migrate and condense analog audio helper and G.711 codec routines into a common class called AnalogAudio for easy reuse; stub network protocol entries to eventually handle analog audio; * add support in the DVM network protocol for analog FM audio traffic; add support to the DVM FNE to properly switch analog FM audio traffic; bump revision from H to J; * add analog enable flag to fne config YAML; * prelim work to make dvmbridge pass analog audio directly into FNE network; * handle locking inside deleteBuffers(); * refactor FrameQueue mutex handling; don't start and stop the thread pool when the peer network opens/closes; * use unique_ptr for compression class instead of passing raw pointers; * refactor FrameQueue from using a std::unordered_map to a simple fixed array that is directly controlled; * refactor compression slightly; * better handle multi-threaded problems; * use a std::vector instead of a classical C allocated array for timestamp list; add error/logic checking for V.24/DFSI where the VHDR may send a TGID0 during a call causing a call rejection; * make timestamps vector static; * dump out of VHDR processing if call is in progress; * bump tarball version build stamp; * add option to forcibly allow TGID0 for badly behaved systems this will cause dvmhost to accept the call and not drop it, with the caveat that the TGID will be rewritten to TGID1, the network stack simply cannot service a TGID of 0 and as such we must rewrite the TGID a TIA-102 valid TGID; * simplify late entry handling where V.24 may send a dstId of 0; * whoops correct my own stupidity; * whoops correct my own stupidity (again); * add opcodes for Harris User Alias and GPS; add opcode for Motorola GPS on PTT; decode Harris User Alias in LDU LCs; * attempt at decoding the LDU RS values for both V.24 and TIA DFSI, this does not abort decoding in the ModemV24 right now and will simply log an error if the RS values for the LDU1 or LDU2 are to badly invalid; * alter field data based on recent testing; enhance and update debug log entries; add debug logging for Voice 1 and 10 start of voice frames for V.24; * add missing tag information for V.24 PDU; * fix incorrect handling of RFSS ID in host setup; * experimental fix to ignore wildly illegal TGIDs in the HDU; * simplify the V.24 HDU TGID logic, entirely ignore the TGID presented in the HDU and just wait for the LDU1 to set the rfLastDstId; * add grantDemand documentation to dvmpatch; * [EXPERIMENTAL] add experimental support for cross-encrypting patched P25 traffic; * reset call algo when a P25 call ends; * properly handle algo ID's for source and destinations; * missed reverseCrypto check; * [EXPERIMENTAL] for some configurations (**you know who you are god damn it**), allow dvmhost to be configured to "idle" on a non-zero TG when the RF talkgroup hangtimer stops, this effectively disables the promiscuous network Rx of the host for certain conventional operations, and requires RF-based TG steering to change the active talkgroup; cleanup the macros for DMR and NXDN that perform various repeated checks; * major refactor for V.24 support; * hide RSSI2 ICW errors; report on any voice frames reporting more then 0 errors; * properly sent VHDR1 and VHDR2 with proper opcodes (whoops); * add experimental support for TDULC over V.24; add some documentation for the V.24 and TIA voice header byte layout; * experimental support for transporting V.24 over IP; * initial super frame should start at 1; make sure to use proper constants instead of magic numbers; * reorganize code slightly; * cleanup format for, and make slightly more precise trace and error dumping log messaging; * add option for the FNE to directly log traffic denials to the system log; * centralize string for illegal RID access; * log total voice frame errors for TIA/DFSI mode; * minor PDU refactoring on when network PDU data is sent; correct issue with accepting a conventional data reg; * disable allowExplicitSourceId by default (not all subscribers support this); correct some bad handling of LC data; * log sysId and netId info for call start and end on the FNE log; * add setting netId and sysId to dvmbridge and dvmpatch for future use; * reorganize and expose decodeLC/encodeLC from LC; correct typo in TDULC headers; add explicit source id TDULC; add LC_GROUP explicit ID flag; * use shorthand macros where able; * enable or disable explicit source ID directly, don't require control to be enabled (important for trunked VC); * minor correction to handling explicit source ID; * properly set dummy SiteData for dvmbridge and dvmpatch (necessary to set the WACN and SysId); * handle condition defaulting WACN and SysId if network data for WACN and SysId is 0; * ensure unencrypted parameters if encryption is disabled; * copy dvmbridge change to dvmpatch for: ensure unencrypted parameters if encryption is disabled; * ignore extra ICW opcodes; * use raw LDU values from the call data instead of processed values if the MFId on the LC isn't TIA standard; * collate bit errors reported from V.24 or TIA; * typos; * allow a group affiliation on Conv+ (DVRS) to terminate a running TG hang timer; * add missing stop to the timer; * add WACN/SysId logging to SysView; * add global to disable transmitting logging to the network (this is used when the UDP socket fails to send data to prevent a crash condition talking to a null socket); unify errno to string message processing for clearity in logs; refactor network reconnect and retry logic to better handle a full connection reset cycle (this is useful for conditions where the network loss isn't transient and is something like a Ethernet or WiFi link drop causing the entire interface to become down temporarily); * remove unnecessary debug logging; * allow encoding user alias at the LC level; * see if we can calculate error percentages; * get rid of magic numbers for properly defined constants; * implement proper support for P25 LDU1 and LDU2 to pass call control bytes; allow a end-point to signal that a call is handing over/switching over from one stream ID/source ID to another; implement support on dvmbridge to properly handle stream/source ID switch over; * log call source switch over events; * whoops this should be a OR equals not equals; * correct issue where network frames would be ignored during RF calls causing buffer overflows; * cleanup and remove unnecessary and confusing C compiler macros; * cleanup and unify design a bit, for P25 move traffic collision checking into separate function calls; * add colorize-host.sh helper tool; update colorize-fne.sh tool for DMR and NXDN; * store status data for a private call; * during unit registration store the originating source peer ID for a given unit registration; use unit registration source ID to select the destination peer to send private call data to; * initial experimental implementation of private call routing: this introduces a new configuration flag "restrictPrivateCallToRegOnly", when set, this flag will influence how private calls are routed through the system private calls using restrictPrivateCallToRegOnly require both the source and destination subscribers to be unit registered with the system, when a private call occurs the system will utilize the source peer ID to restrict repeating the private call traffic only to the 2 peers involved in the private call FNEs will *always* receive private call traffic * rework stream validation for U2U calls slightly for P25; ensure P25 always sends the U2U control byte for private calls; * enhance handling of NXDN LC on the FNE; fix issue where dvmhost would not process a network RCCH burst; * refactor NXDN CC handling a bit; * add support to translate a raw DENY/QUEUE/CAUSE/REASON value for DMR/P25/NXDN into a human readable string; * whoops hastily missed std::string -> c_str conversion; * ensure SSRC is maintained for unit registration announcements; * more implementation for private call routing; * add missing clear flag to DMR payload activate RPC; * correct crash for fsc set to true when not using DFSI TIA-102/UDP; * add support to disable a failed CRC-32 for P25 PDU data; ignore CRC-32 errors for AMBT PDUs; * Add support in dvmbridge for a serial PTT activation switch. RTS is asserted on the serial port defined in bridge-config.yml for the duration of audio received, then is removed. (#102) * P25 PDU packet handling refactor (for future use); * remove test code; * fix issue where PDU RSPs weren't being sent to the FNE; correct timing around ARP and packet retry when subscriber is not ready; * add some more verbose logging; * experimental changes to PDU data handling; * correct CSV parsing for iden, peer list and RID lookup tables (we would skip parameters if they were empty); make the FNE P25 packet data handler operate in the TIA-102 asymmetric addressing mode (as would be required with FNE configurations); --------- Co-authored-by: Jamie <25770089+faultywarrior@users.noreply.github.com> Co-authored-by: faulty <faulty@evilcomputing.net> Co-authored-by: Lorenzo L. Romero <lorenzolrom@gmail.com>4.32j_maint 2025-09-03
parent
fcdec00f32
commit
7c2bfb3914
@ -0,0 +1,14 @@
|
||||
#
|
||||
# Digital Voice Modem - Adj. Site Map
|
||||
#
|
||||
|
||||
#
|
||||
# Peer List
|
||||
#
|
||||
peers:
|
||||
# Peer ID.
|
||||
- peerId: 1234567
|
||||
# Flag indicating whether this talkgroup is active or not.
|
||||
active: true
|
||||
# List of peer IDs that are neighbors for this peer.
|
||||
neighbors: []
|
||||
@ -1,9 +1,9 @@
|
||||
#
|
||||
# This file sets the valid peer IDs allowed on a FNE.
|
||||
#
|
||||
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),<newline>"
|
||||
#1234,,0,,1,
|
||||
#5678,MYSECUREPASSWORD,0,,0,
|
||||
#9876,MYSECUREPASSWORD,1,,0,
|
||||
#5432,MYSECUREPASSWORD,,Peer Alias 1,0,
|
||||
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,
|
||||
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled)<newline>"
|
||||
#1234,,0,,1,0,
|
||||
#5678,MYSECUREPASSWORD,0,,0,0,
|
||||
#9876,MYSECUREPASSWORD,1,,0,0,
|
||||
#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,
|
||||
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,264 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Bridge
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
|
||||
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "RtsPttController.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the RtsPttController class. */
|
||||
|
||||
RtsPttController::RtsPttController(const std::string& port) :
|
||||
m_port(port),
|
||||
m_isOpen(false),
|
||||
#if defined(_WIN32)
|
||||
m_fd(INVALID_HANDLE_VALUE)
|
||||
#else
|
||||
m_fd(-1)
|
||||
#endif // defined(_WIN32)
|
||||
{
|
||||
assert(!port.empty());
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the RtsPttController class. */
|
||||
|
||||
RtsPttController::~RtsPttController()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
/* Opens the serial port for RTS control. */
|
||||
|
||||
bool RtsPttController::open()
|
||||
{
|
||||
if (m_isOpen)
|
||||
return true;
|
||||
|
||||
#if defined(_WIN32)
|
||||
assert(m_fd == INVALID_HANDLE_VALUE);
|
||||
|
||||
std::string deviceName = m_port;
|
||||
if (deviceName.find("\\\\.\\") == std::string::npos) {
|
||||
deviceName = "\\\\.\\" + m_port;
|
||||
}
|
||||
|
||||
m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (m_fd == INVALID_HANDLE_VALUE) {
|
||||
::LogError(LOG_HOST, "Cannot open RTS PTT device - %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
DCB dcb;
|
||||
if (::GetCommState(m_fd, &dcb) == 0) {
|
||||
::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
::CloseHandle(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
dcb.BaudRate = 9600;
|
||||
dcb.ByteSize = 8;
|
||||
dcb.Parity = NOPARITY;
|
||||
dcb.fParity = FALSE;
|
||||
dcb.StopBits = ONESTOPBIT;
|
||||
dcb.fInX = FALSE;
|
||||
dcb.fOutX = FALSE;
|
||||
dcb.fOutxCtsFlow = FALSE;
|
||||
dcb.fOutxDsrFlow = FALSE;
|
||||
dcb.fDsrSensitivity = FALSE;
|
||||
dcb.fDtrControl = DTR_CONTROL_DISABLE;
|
||||
dcb.fRtsControl = RTS_CONTROL_DISABLE;
|
||||
|
||||
if (::SetCommState(m_fd, &dcb) == 0) {
|
||||
::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
::CloseHandle(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear RTS initially
|
||||
if (::EscapeCommFunction(m_fd, CLRRTS) == 0) {
|
||||
::LogError(LOG_HOST, "Cannot clear RTS for %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
::CloseHandle(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
assert(m_fd == -1);
|
||||
|
||||
m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0);
|
||||
if (m_fd < 0) {
|
||||
::LogError(LOG_HOST, "Cannot open RTS PTT device - %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::isatty(m_fd) == 0) {
|
||||
::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str());
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!setTermios()) {
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
return false;
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
::LogInfo(LOG_HOST, "RTS PTT Controller opened on %s", m_port.c_str());
|
||||
m_isOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Closes the serial port. */
|
||||
|
||||
void RtsPttController::close()
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return;
|
||||
|
||||
// Clear RTS before closing
|
||||
clearPTT();
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (m_fd != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#else
|
||||
if (m_fd != -1) {
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
m_isOpen = false;
|
||||
::LogInfo(LOG_HOST, "RTS PTT Controller closed");
|
||||
}
|
||||
|
||||
/* Sets RTS signal high (asserts RTS) to trigger PTT. */
|
||||
|
||||
bool RtsPttController::setPTT()
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return false;
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (::EscapeCommFunction(m_fd, SETRTS) == 0) {
|
||||
::LogError(LOG_HOST, "Cannot set RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
uint32_t y;
|
||||
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
y |= TIOCM_RTS;
|
||||
|
||||
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot set RTS PTT for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
::LogDebug(LOG_HOST, "RTS PTT asserted on %s", m_port.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Sets RTS signal low (clears RTS) to release PTT. */
|
||||
|
||||
bool RtsPttController::clearPTT()
|
||||
{
|
||||
if (!m_isOpen)
|
||||
return false;
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (::EscapeCommFunction(m_fd, CLRRTS) == 0) {
|
||||
::LogError(LOG_HOST, "Cannot clear RTS PTT for %s, err=%04lx", m_port.c_str(), ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
uint32_t y;
|
||||
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
y &= ~TIOCM_RTS;
|
||||
|
||||
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot clear RTS PTT for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
::LogDebug(LOG_HOST, "RTS PTT cleared on %s", m_port.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Sets the termios settings on the serial port. */
|
||||
|
||||
bool RtsPttController::setTermios()
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
termios termios;
|
||||
if (::tcgetattr(m_fd, &termios) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK);
|
||||
termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL);
|
||||
termios.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||
termios.c_oflag &= ~(OPOST);
|
||||
termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
|
||||
termios.c_cflag |= (CS8 | CLOCAL | CREAD);
|
||||
termios.c_lflag &= ~(ISIG | ICANON | IEXTEN);
|
||||
termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
|
||||
termios.c_cc[VMIN] = 0;
|
||||
termios.c_cc[VTIME] = 10;
|
||||
|
||||
::cfsetospeed(&termios, B9600);
|
||||
::cfsetispeed(&termios, B9600);
|
||||
|
||||
if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear RTS initially
|
||||
uint32_t y;
|
||||
if (::ioctl(m_fd, TIOCMGET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
y &= ~TIOCM_RTS;
|
||||
|
||||
if (::ioctl(m_fd, TIOCMSET, &y) < 0) {
|
||||
::LogError(LOG_HOST, "Cannot clear RTS for %s", m_port.c_str());
|
||||
return false;
|
||||
}
|
||||
#endif // !defined(_WIN32)
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Bridge
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
|
||||
* Copyright (C) 2025 Lorenzo L. Romero, K2LLR
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file RtsPttController.h
|
||||
* @ingroup bridge
|
||||
* @file RtsPttController.cpp
|
||||
* @ingroup bridge
|
||||
*/
|
||||
#if !defined(__RTS_PTT_CONTROLLER_H__)
|
||||
#define __RTS_PTT_CONTROLLER_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "common/Log.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief This class implements RTS PTT control for the bridge.
|
||||
* @ingroup bridge
|
||||
*/
|
||||
class HOST_SW_API RtsPttController {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the RtsPttController class.
|
||||
* @param port Serial port device (e.g., /dev/ttyUSB0).
|
||||
*/
|
||||
RtsPttController(const std::string& port);
|
||||
/**
|
||||
* @brief Finalizes a instance of the RtsPttController class.
|
||||
*/
|
||||
~RtsPttController();
|
||||
|
||||
/**
|
||||
* @brief Opens the serial port for RTS control.
|
||||
* @returns bool True, if port was opened successfully, otherwise false.
|
||||
*/
|
||||
bool open();
|
||||
/**
|
||||
* @brief Closes the serial port.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief Sets RTS signal high (asserts RTS) to trigger PTT.
|
||||
* @returns bool True, if RTS was set successfully, otherwise false.
|
||||
*/
|
||||
bool setPTT();
|
||||
/**
|
||||
* @brief Sets RTS signal low (clears RTS) to release PTT.
|
||||
* @returns bool True, if RTS was cleared successfully, otherwise false.
|
||||
*/
|
||||
bool clearPTT();
|
||||
|
||||
private:
|
||||
std::string m_port;
|
||||
bool m_isOpen;
|
||||
#if defined(_WIN32)
|
||||
HANDLE m_fd;
|
||||
#else
|
||||
int m_fd;
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
/**
|
||||
* @brief Sets the termios settings on the serial port.
|
||||
* @returns bool True, if settings are set, otherwise false.
|
||||
*/
|
||||
bool setTermios();
|
||||
};
|
||||
|
||||
#endif // __RTS_PTT_CONTROLLER_H__
|
||||
@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Bridge
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file SampleTimeConversion.h
|
||||
* @ingroup bridge
|
||||
*/
|
||||
#if !defined(__SAMPLE_TIME_CONVERSION_H__)
|
||||
#define __SAMPLE_TIME_CONVERSION_H__
|
||||
|
||||
#include "Defines.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @ingroup bridge
|
||||
*/
|
||||
class HOST_SW_API SampleTimeConvert {
|
||||
public:
|
||||
/**
|
||||
* @brief (ms) to sample count conversion
|
||||
* @param sampleRate Sample rate.
|
||||
* @param channels Number of audio channels.
|
||||
* @param ms Number of milliseconds.
|
||||
* @returns int Number of samples.
|
||||
*/
|
||||
static int ToSamples(uint32_t sampleRate, uint8_t channels, int ms)
|
||||
{
|
||||
return (int)(((long)ms) * sampleRate * channels / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sample count to (ms) conversion
|
||||
* @param sampleRate Sample rate.
|
||||
* @param channels Number of audio channels.
|
||||
* @param samples Number of samples.
|
||||
* @returns int Number of milliseconds.
|
||||
*/
|
||||
static int ToMS(uint32_t sampleRate, uint8_t channels, int samples)
|
||||
{
|
||||
return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __SAMPLE_TIME_CONVERSION_H__
|
||||
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Bridge
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
|
||||
#define IDS_APP_TITLE 102
|
||||
#define IDI_APP_ICON 103
|
||||
Binary file not shown.
@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "analog/AnalogAudio.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace analog;
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SIGN_BIT (0x80) // sign bit for a A-law byte
|
||||
#define QUANT_MASK (0xf) // quantization field mask
|
||||
#define NSEGS (8) // number of A-law segments
|
||||
#define SEG_SHIFT (4) // left shift for segment number
|
||||
#define SEG_MASK (0x70) // segment field mask
|
||||
|
||||
static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF };
|
||||
static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF };
|
||||
|
||||
#define BIAS (0x84) // bias for linear code
|
||||
#define CLIP 8159
|
||||
|
||||
#define SHORT_CLIP_SMPL_MIN -32767
|
||||
#define SHORT_CLIP_SMPL_MAX 32767
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Static Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Helper to convert PCM into G.711 aLaw. */
|
||||
|
||||
uint8_t AnalogAudio::encodeALaw(short pcm)
|
||||
{
|
||||
short mask;
|
||||
uint8_t aval;
|
||||
|
||||
pcm = pcm >> 3;
|
||||
|
||||
if (pcm >= 0) {
|
||||
mask = 0xD5U; // sign (7th) bit = 1
|
||||
} else {
|
||||
mask = 0x55U; // sign bit = 0
|
||||
pcm = -pcm - 1;
|
||||
}
|
||||
|
||||
// convert the scaled magnitude to segment number
|
||||
short seg = search(pcm, seg_aend, 8);
|
||||
|
||||
/*
|
||||
** combine the sign, segment, quantization bits
|
||||
*/
|
||||
if (seg >= 8) // out of range, return maximum value
|
||||
return (uint8_t)(0x7F ^ mask);
|
||||
else {
|
||||
aval = (uint8_t) seg << SEG_SHIFT;
|
||||
if (seg < 2)
|
||||
aval |= (pcm >> 1) & QUANT_MASK;
|
||||
else
|
||||
aval |= (pcm >> seg) & QUANT_MASK;
|
||||
|
||||
return (aval ^ mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper to convert G.711 aLaw into PCM. */
|
||||
|
||||
short AnalogAudio::decodeALaw(uint8_t alaw)
|
||||
{
|
||||
alaw ^= 0x55U;
|
||||
|
||||
short t = (alaw & QUANT_MASK) << 4;
|
||||
short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
t += 8;
|
||||
break;
|
||||
case 1:
|
||||
t += 0x108U;
|
||||
break;
|
||||
default:
|
||||
t += 0x108U;
|
||||
t <<= seg - 1;
|
||||
}
|
||||
|
||||
return ((alaw & SIGN_BIT) ? t : -t);
|
||||
}
|
||||
|
||||
/* Helper to convert PCM into G.711 MuLaw. */
|
||||
|
||||
uint8_t AnalogAudio::encodeMuLaw(short pcm)
|
||||
{
|
||||
short mask;
|
||||
|
||||
// get the sign and the magnitude of the value
|
||||
pcm = pcm >> 2;
|
||||
if (pcm < 0) {
|
||||
pcm = -pcm;
|
||||
mask = 0x7FU;
|
||||
} else {
|
||||
mask = 0xFFU;
|
||||
}
|
||||
|
||||
// clip the magnitude
|
||||
if (pcm > CLIP)
|
||||
pcm = CLIP;
|
||||
pcm += (BIAS >> 2);
|
||||
|
||||
// convert the scaled magnitude to segment number
|
||||
short seg = search(pcm, seg_uend, 8);
|
||||
|
||||
/*
|
||||
** combine the sign, segment, quantization bits;
|
||||
** and complement the code word
|
||||
*/
|
||||
if (seg >= 8) // out of range, return maximum value.
|
||||
return (uint8_t)(0x7F ^ mask);
|
||||
else {
|
||||
uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF);
|
||||
return (ulaw ^ mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper to convert G.711 MuLaw into PCM. */
|
||||
|
||||
short AnalogAudio::decodeMuLaw(uint8_t ulaw)
|
||||
{
|
||||
// complement to obtain normal u-law value
|
||||
ulaw = ~ulaw;
|
||||
|
||||
/*
|
||||
** extract and bias the quantization bits; then
|
||||
** shift up by the segment number and subtract out the bias
|
||||
*/
|
||||
short t = ((ulaw & QUANT_MASK) << 3) + BIAS;
|
||||
t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT;
|
||||
|
||||
return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
|
||||
}
|
||||
|
||||
/* Helper to apply linear gain to PCM samples. */
|
||||
|
||||
void AnalogAudio::gain(short* pcm, int length, float gain)
|
||||
{
|
||||
assert(pcm != nullptr);
|
||||
assert(length > 0);
|
||||
|
||||
if (gain == 1.0f)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int sample = static_cast<int>(pcm[i] * gain);
|
||||
if (sample > SHORT_CLIP_SMPL_MAX)
|
||||
sample = SHORT_CLIP_SMPL_MAX;
|
||||
else if (sample < SHORT_CLIP_SMPL_MIN)
|
||||
sample = SHORT_CLIP_SMPL_MIN;
|
||||
|
||||
pcm[i] = static_cast<short>(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Static Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Searches for the segment in the given array. */
|
||||
|
||||
short AnalogAudio::search(short val, short* table, short size)
|
||||
{
|
||||
for (short i = 0; i < size; i++) {
|
||||
if (val <= *table++)
|
||||
return (i);
|
||||
}
|
||||
|
||||
return (size);
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024,2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file AnalogAudio.h
|
||||
* @ingroup analog
|
||||
* @file AnalogAudio.cpp
|
||||
* @ingroup analog
|
||||
*/
|
||||
#if !defined(__ANALOG_AUDIO_H__)
|
||||
#define __ANALOG_AUDIO_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/analog/AnalogDefines.h"
|
||||
|
||||
namespace analog
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements aLaw and uLaw audio codecs, along with helper routines for analog audio.
|
||||
* @ingroup analog
|
||||
*/
|
||||
class HOST_SW_API AnalogAudio {
|
||||
public:
|
||||
/**
|
||||
* @brief Helper to convert PCM into G.711 aLaw.
|
||||
* @param pcm PCM value.
|
||||
* @return uint8_t aLaw value.
|
||||
*/
|
||||
static uint8_t encodeALaw(short pcm);
|
||||
/**
|
||||
* @brief Helper to convert G.711 aLaw into PCM.
|
||||
* @param alaw aLaw value.
|
||||
* @return short PCM value.
|
||||
*/
|
||||
static short decodeALaw(uint8_t alaw);
|
||||
/**
|
||||
* @brief Helper to convert PCM into G.711 MuLaw.
|
||||
* @param pcm PCM value.
|
||||
* @return uint8_t MuLaw value.
|
||||
*/
|
||||
static uint8_t encodeMuLaw(short pcm);
|
||||
/**
|
||||
* @brief Helper to convert G.711 MuLaw into PCM.
|
||||
* @param ulaw MuLaw value.
|
||||
* @return short PCM value.
|
||||
*/
|
||||
static short decodeMuLaw(uint8_t ulaw);
|
||||
|
||||
/**
|
||||
* @brief Helper to apply linear gain to PCM samples.
|
||||
* @param pcm Buffer containing PCM samples.
|
||||
* @param length Length of the PCM buffer in samples.
|
||||
* @param gain Gain factor to apply to the PCM samples (1.0f = no change).
|
||||
*/
|
||||
static void gain(short *pcm, int length, float gain);
|
||||
|
||||
/**
|
||||
* @brief (ms) to sample count conversion.
|
||||
* @param sampleRate Sample rate.
|
||||
* @param channels Number of audio channels.
|
||||
* @param ms Number of milliseconds.
|
||||
* @returns int Number of samples.
|
||||
*/
|
||||
static int toSamples(uint32_t sampleRate, uint8_t channels, int ms) { return (int)(((long)ms) * sampleRate * channels / 1000); }
|
||||
|
||||
/**
|
||||
* @brief Sample count to (ms) conversion.
|
||||
* @param sampleRate Sample rate.
|
||||
* @param channels Number of audio channels.
|
||||
* @param samples Number of samples.
|
||||
* @returns int Number of milliseconds.
|
||||
*/
|
||||
static int toMS(uint32_t sampleRate, uint8_t channels, int samples) { return (int)(((float)samples / (float)sampleRate / (float)channels) * 1000); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Searches for the segment in the given array.
|
||||
* @param val PCM value to search for.
|
||||
* @param table Array of segment values.
|
||||
* @param size Size of the segment array.
|
||||
* @return short
|
||||
*/
|
||||
static short search(short val, short* table, short size);
|
||||
};
|
||||
} // namespace analog
|
||||
|
||||
#endif // __ANALOG_AUDIO_H__
|
||||
@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @defgroup analog Digital Mobile Radio
|
||||
* @brief Defines and implements aLaw and uLaw audio codecs, along with helper routines for analog audio.
|
||||
* @ingroup common
|
||||
*
|
||||
* @file AnalogDefines.h
|
||||
* @ingroup analog
|
||||
*/
|
||||
#if !defined(__ANALOG_DEFINES_H__)
|
||||
#define __ANALOG_DEFINES_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
|
||||
// Shorthand macro to analog::defines -- keeps source code that doesn't use "using" concise
|
||||
#if !defined(ANODEF)
|
||||
#define ANODEF analog::defines
|
||||
#endif // DMRDEF
|
||||
namespace analog
|
||||
{
|
||||
namespace defines
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup analog
|
||||
* @{
|
||||
*/
|
||||
|
||||
const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz.
|
||||
const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes.
|
||||
/** @} */
|
||||
|
||||
/** @brief Audio Frame Type(s) */
|
||||
namespace AudioFrameType {
|
||||
/** @brief Audio Frame Type(s) */
|
||||
enum E : uint8_t {
|
||||
VOICE_START = 0x00U, //! Voice Start Frame
|
||||
VOICE = 0x01U, //! Voice Continuation Frame
|
||||
TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator
|
||||
};
|
||||
}
|
||||
|
||||
#define ANO_TERMINATOR "Analog, TERMINATOR (Terminator)"
|
||||
#define ANO_VOICE "Analog, VOICE (Voice Data)"
|
||||
} // namespace defines
|
||||
} // namespace analog
|
||||
|
||||
#endif // __ANALOG_DEFINES_H__
|
||||
@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "analog/AnalogAudio.h"
|
||||
#include "analog/data/NetData.h"
|
||||
|
||||
using namespace analog;
|
||||
using namespace analog::defines;
|
||||
using namespace analog::data;
|
||||
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the NetData class. */
|
||||
|
||||
NetData::NetData(const NetData& data) :
|
||||
m_srcId(data.m_srcId),
|
||||
m_dstId(data.m_dstId),
|
||||
m_control(data.m_control),
|
||||
m_seqNo(data.m_seqNo),
|
||||
m_group(true),
|
||||
m_frameType(data.m_frameType),
|
||||
m_audio(nullptr)
|
||||
{
|
||||
m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES];
|
||||
::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
/* Initializes a new instance of the NetData class. */
|
||||
|
||||
NetData::NetData() :
|
||||
m_srcId(0U),
|
||||
m_dstId(0U),
|
||||
m_control(0U),
|
||||
m_seqNo(0U),
|
||||
m_group(true),
|
||||
m_frameType(AudioFrameType::TERMINATOR),
|
||||
m_audio(nullptr)
|
||||
{
|
||||
m_audio = new uint8_t[2U * AUDIO_SAMPLES_LENGTH_BYTES];
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the NetData class. */
|
||||
|
||||
NetData::~NetData()
|
||||
{
|
||||
delete[] m_audio;
|
||||
}
|
||||
|
||||
/* Equals operator. */
|
||||
|
||||
NetData& NetData::operator=(const NetData& data)
|
||||
{
|
||||
if (this != &data) {
|
||||
::memcpy(m_audio, data.m_audio, 2U * AUDIO_SAMPLES_LENGTH_BYTES);
|
||||
|
||||
m_srcId = data.m_srcId;
|
||||
m_dstId = data.m_dstId;
|
||||
m_control = data.m_control;
|
||||
m_frameType = data.m_frameType;
|
||||
m_seqNo = data.m_seqNo;
|
||||
m_group = data.m_group;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Sets audio data. */
|
||||
|
||||
void NetData::setAudio(const uint8_t* buffer)
|
||||
{
|
||||
assert(buffer != nullptr);
|
||||
|
||||
::memcpy(m_audio, buffer, AUDIO_SAMPLES_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
/* Gets audio data. */
|
||||
|
||||
uint32_t NetData::getAudio(uint8_t* buffer) const
|
||||
{
|
||||
assert(buffer != nullptr);
|
||||
|
||||
::memcpy(buffer, m_audio, AUDIO_SAMPLES_LENGTH_BYTES);
|
||||
|
||||
return AUDIO_SAMPLES_LENGTH_BYTES;
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file NetData.h
|
||||
* @ingroup analog
|
||||
* @file NetData.cpp
|
||||
* @ingroup analog
|
||||
*/
|
||||
#if !defined(__ANALOG_DATA__NET_DATA_H__)
|
||||
#define __ANALOG_DATA__NET_DATA_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/analog/AnalogDefines.h"
|
||||
|
||||
namespace analog
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Represents network analog data. When setting audio data, it is expected to be 20ms of 16-bit
|
||||
* audio at 8kHz, or 320 bytes in length.
|
||||
* @ingroup analog
|
||||
*/
|
||||
class HOST_SW_API NetData {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the NetData class.
|
||||
* @param data Instance of NetData class to copy from.
|
||||
*/
|
||||
NetData(const NetData& data);
|
||||
/**
|
||||
* @brief Initializes a new instance of the NetData class.
|
||||
*/
|
||||
NetData();
|
||||
/**
|
||||
* @brief Finalizes a instance of the NetData class.
|
||||
*/
|
||||
~NetData();
|
||||
|
||||
/**
|
||||
* @brief Equals operator.
|
||||
* @param data Instance of NetData class to copy from.
|
||||
*/
|
||||
NetData& operator=(const NetData& data);
|
||||
|
||||
/**
|
||||
* @brief Sets audio data. This data buffer should be MuLaw encoded.
|
||||
* @param[in] buffer Audio data buffer.
|
||||
*/
|
||||
void setAudio(const uint8_t* buffer);
|
||||
/**
|
||||
* @brief Gets audio data.
|
||||
* @param[out] buffer Audio data buffer.
|
||||
*/
|
||||
uint32_t getAudio(uint8_t* buffer) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Source ID.
|
||||
*/
|
||||
DECLARE_PROPERTY(uint32_t, srcId, SrcId);
|
||||
/**
|
||||
* @brief Destination ID.
|
||||
*/
|
||||
DECLARE_PROPERTY(uint32_t, dstId, DstId);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
DECLARE_PROPERTY(uint8_t, control, Control);
|
||||
|
||||
/**
|
||||
* @brief Sequence number.
|
||||
*/
|
||||
DECLARE_PROPERTY(uint8_t, seqNo, SeqNo);
|
||||
|
||||
/**
|
||||
* @brief Flag indicatin if this group audio or individual audio.
|
||||
*/
|
||||
DECLARE_PROPERTY(bool, group, Group);
|
||||
|
||||
/**
|
||||
* @brief Audio frame type.
|
||||
*/
|
||||
DECLARE_PROPERTY(defines::AudioFrameType::E, frameType, FrameType);
|
||||
|
||||
private:
|
||||
uint8_t* m_audio;
|
||||
};
|
||||
} // namespace data
|
||||
} // namespace analog
|
||||
|
||||
#endif // __ANALOG_DATA__NET_DATA_H__
|
||||
@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "Defines.h"
|
||||
#include "dmr/DMRDefines.h"
|
||||
#include "dmr/DMRUtils.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace dmr;
|
||||
using namespace dmr::defines;
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Helper to convert a denial reason code to a string. */
|
||||
|
||||
std::string DMRUtils::rsnToString(uint8_t reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case ReasonCode::TS_ACK_RSN_MSG:
|
||||
return std::string("TS_ACK_RSN_MSG (Message Accepted)");
|
||||
case ReasonCode::TS_ACK_RSN_REG:
|
||||
return std::string("TS_ACK_RSN_REG (Registration Accepted)");
|
||||
case ReasonCode::TS_ACK_RSN_AUTH_RESP:
|
||||
return std::string("TS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)");
|
||||
case ReasonCode::TS_ACK_RSN_REG_SUB_ATTACH:
|
||||
return std::string("TS_ACK_RSN_REG_SUB_ATTACH (Registration Response with subscription)");
|
||||
case ReasonCode::MS_ACK_RSN_MSG:
|
||||
return std::string("MS_ACK_RSN_MSG (Message Accepted)");
|
||||
case ReasonCode::MS_ACK_RSN_AUTH_RESP:
|
||||
return std::string("MS_ACK_RSN_AUTH_RESP (Authentication Challenge Response)");
|
||||
|
||||
case ReasonCode::TS_DENY_RSN_SYS_UNSUPPORTED_SVC:
|
||||
return std::string("TS_DENY_RSN_SYS_UNSUPPORTED_SVC (System Unsupported Service)");
|
||||
case ReasonCode::TS_DENY_RSN_PERM_USER_REFUSED:
|
||||
return std::string("TS_DENY_RSN_PERM_USER_REFUSED (User Permenantly Refused)");
|
||||
case ReasonCode::TS_DENY_RSN_TEMP_USER_REFUSED:
|
||||
return std::string("TS_DENY_RSN_TEMP_USER_REFUSED (User Temporarily Refused)");
|
||||
case ReasonCode::TS_DENY_RSN_TRSN_SYS_REFUSED:
|
||||
return std::string("TS_DENY_RSN_TRSN_SYS_REFUSED (System Refused)");
|
||||
case ReasonCode::TS_DENY_RSN_TGT_NOT_REG:
|
||||
return std::string("TS_DENY_RSN_TGT_NOT_REG (Target Not Registered)");
|
||||
case ReasonCode::TS_DENY_RSN_TGT_UNAVAILABLE:
|
||||
return std::string("TS_DENY_RSN_TGT_UNAVAILABLE (Target Unavailable)");
|
||||
case ReasonCode::TS_DENY_RSN_SYS_BUSY:
|
||||
return std::string("TS_DENY_RSN_SYS_BUSY (System Busy)");
|
||||
case ReasonCode::TS_DENY_RSN_SYS_NOT_READY:
|
||||
return std::string("TS_DENY_RSN_SYS_NOT_READY (System Not Ready)");
|
||||
case ReasonCode::TS_DENY_RSN_CALL_CNCL_REFUSED:
|
||||
return std::string("TS_DENY_RSN_CALL_CNCL_REFUSED (Call Cancel Refused)");
|
||||
case ReasonCode::TS_DENY_RSN_REG_REFUSED:
|
||||
return std::string("TS_DENY_RSN_REG_REFUSED (Registration Refused)");
|
||||
case ReasonCode::TS_DENY_RSN_REG_DENIED:
|
||||
return std::string("TS_DENY_RSN_REG_DENIED (Registration Denied)");
|
||||
case ReasonCode::TS_DENY_RSN_MS_NOT_REG:
|
||||
return std::string("TS_DENY_RSN_MS_NOT_REG (MS Not Registered)");
|
||||
case ReasonCode::TS_DENY_RSN_TGT_BUSY:
|
||||
return std::string("TS_DENY_RSN_TGT_BUSY (Target Busy)");
|
||||
case ReasonCode::TS_DENY_RSN_TGT_GROUP_NOT_VALID:
|
||||
return std::string("TS_DENY_RSN_TGT_GROUP_NOT_VALID (Group Not Valid)");
|
||||
|
||||
case ReasonCode::TS_QUEUED_RSN_NO_RESOURCE:
|
||||
return std::string("TS_QUEUED_RSN_NO_RESOURCE (No Resources Available)");
|
||||
case ReasonCode::TS_QUEUED_RSN_SYS_BUSY:
|
||||
return std::string("TS_QUEUED_RSN_SYS_BUSY (System Busy)");
|
||||
|
||||
case ReasonCode::TS_WAIT_RSN:
|
||||
return std::string("TS_WAIT_RSN (Wait)");
|
||||
|
||||
case ReasonCode::MS_DENY_RSN_UNSUPPORTED_SVC:
|
||||
return std::string("MS_DENY_RSN_UNSUPPORTED_SVC (Service Unsupported)");
|
||||
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,288 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "lookups/AdjSiteMapLookup.h"
|
||||
#include "Log.h"
|
||||
#include "Timer.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace lookups;
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::mutex AdjSiteMapLookup::m_mutex;
|
||||
bool AdjSiteMapLookup::m_locked = false;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Lock the table.
|
||||
#define __LOCK_TABLE() \
|
||||
std::lock_guard<std::mutex> lock(m_mutex); \
|
||||
m_locked = true;
|
||||
|
||||
// Unlock the table.
|
||||
#define __UNLOCK_TABLE() m_locked = false;
|
||||
|
||||
// Spinlock wait for table to be read unlocked.
|
||||
#define __SPINLOCK() \
|
||||
if (m_locked) { \
|
||||
while (m_locked) \
|
||||
Thread::sleep(2U); \
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the AdjSiteMapLookup class. */
|
||||
|
||||
AdjSiteMapLookup::AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime) : Thread(),
|
||||
m_rulesFile(filename),
|
||||
m_reloadTime(reloadTime),
|
||||
m_rules(),
|
||||
m_stop(false),
|
||||
m_adjPeerMap()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the AdjSiteMapLookup class. */
|
||||
|
||||
AdjSiteMapLookup::~AdjSiteMapLookup() = default;
|
||||
|
||||
/* Thread entry point. This function is provided to run the thread for the lookup table. */
|
||||
|
||||
void AdjSiteMapLookup::entry()
|
||||
{
|
||||
if (m_reloadTime == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
Timer timer(1U, 60U * m_reloadTime);
|
||||
timer.start();
|
||||
|
||||
while (!m_stop) {
|
||||
sleep(1000U);
|
||||
|
||||
timer.clock();
|
||||
if (timer.hasExpired()) {
|
||||
load();
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Stops and unloads this lookup table. */
|
||||
|
||||
void AdjSiteMapLookup::stop(bool noDestroy)
|
||||
{
|
||||
if (m_reloadTime == 0U) {
|
||||
if (!noDestroy)
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
m_stop = true;
|
||||
|
||||
wait();
|
||||
}
|
||||
|
||||
/* Reads the lookup table from the specified lookup table file. */
|
||||
|
||||
bool AdjSiteMapLookup::read()
|
||||
{
|
||||
bool ret = load();
|
||||
|
||||
if (m_reloadTime > 0U)
|
||||
run();
|
||||
setName("host:adj-site-map");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Clears all entries from the lookup table. */
|
||||
|
||||
void AdjSiteMapLookup::clear()
|
||||
{
|
||||
__LOCK_TABLE();
|
||||
|
||||
m_adjPeerMap.clear();
|
||||
|
||||
__UNLOCK_TABLE();
|
||||
}
|
||||
|
||||
/* Adds a new entry to the lookup table by the specified unique ID. */
|
||||
|
||||
void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry)
|
||||
{
|
||||
uint32_t id = entry.peerId();
|
||||
|
||||
__LOCK_TABLE();
|
||||
|
||||
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
|
||||
[&](AdjPeerMapEntry x)
|
||||
{
|
||||
return x.peerId() == id;
|
||||
});
|
||||
if (it != m_adjPeerMap.end()) {
|
||||
m_adjPeerMap[it - m_adjPeerMap.begin()] = entry;
|
||||
}
|
||||
else {
|
||||
m_adjPeerMap.push_back(entry);
|
||||
}
|
||||
|
||||
__UNLOCK_TABLE();
|
||||
}
|
||||
|
||||
/* Erases an existing entry from the lookup table by the specified unique ID. */
|
||||
|
||||
void AdjSiteMapLookup::eraseEntry(uint32_t id)
|
||||
{
|
||||
__LOCK_TABLE();
|
||||
|
||||
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; });
|
||||
if (it != m_adjPeerMap.end()) {
|
||||
m_adjPeerMap.erase(it);
|
||||
}
|
||||
|
||||
__UNLOCK_TABLE();
|
||||
}
|
||||
|
||||
/* Finds a table entry in this lookup table. */
|
||||
|
||||
AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id)
|
||||
{
|
||||
AdjPeerMapEntry entry;
|
||||
|
||||
__SPINLOCK();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(),
|
||||
[&](AdjPeerMapEntry x)
|
||||
{
|
||||
return x.peerId() == id;
|
||||
});
|
||||
if (it != m_adjPeerMap.end()) {
|
||||
entry = *it;
|
||||
} else {
|
||||
entry = AdjPeerMapEntry();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/* Saves loaded talkgroup rules. */
|
||||
|
||||
bool AdjSiteMapLookup::commit()
|
||||
{
|
||||
return save();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Loads the table from the passed lookup table file. */
|
||||
|
||||
bool AdjSiteMapLookup::load()
|
||||
{
|
||||
if (m_rulesFile.length() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
bool ret = yaml::Parse(m_rules, m_rulesFile.c_str());
|
||||
if (!ret) {
|
||||
LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s - error parsing YML", m_rulesFile.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (yaml::OperationException const& e) {
|
||||
LogError(LOG_HOST, "Cannot open the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear table
|
||||
clear();
|
||||
|
||||
__LOCK_TABLE();
|
||||
|
||||
yaml::Node& peerList = m_rules["peers"];
|
||||
|
||||
if (peerList.size() == 0U) {
|
||||
::LogError(LOG_HOST, "No adj site map peer list defined!");
|
||||
m_locked = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < peerList.size(); i++) {
|
||||
AdjPeerMapEntry entry = AdjPeerMapEntry(peerList[i]);
|
||||
m_adjPeerMap.push_back(entry);
|
||||
}
|
||||
|
||||
__UNLOCK_TABLE();
|
||||
|
||||
size_t size = m_adjPeerMap.size();
|
||||
if (size == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfoEx(LOG_HOST, "Loaded %lu entries into adjacent site map table", size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Saves the table to the passed lookup table file. */
|
||||
|
||||
bool AdjSiteMapLookup::save()
|
||||
{
|
||||
// Make sure file is valid
|
||||
if (m_rulesFile.length() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// New list for our new group voice rules
|
||||
yaml::Node peerList;
|
||||
yaml::Node newRules;
|
||||
|
||||
for (auto entry : m_adjPeerMap) {
|
||||
yaml::Node& gv = peerList.push_back();
|
||||
entry.getYaml(gv);
|
||||
}
|
||||
|
||||
// Set the new rules
|
||||
newRules["peers"] = peerList;
|
||||
|
||||
// Make sure we actually did stuff right
|
||||
if (newRules["peers"].size() != m_adjPeerMap.size()) {
|
||||
LogError(LOG_HOST, "Generated YAML node for group lists did not match loaded map size! (%u != %u)", newRules["peers"].size(), m_adjPeerMap.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str());
|
||||
yaml::Serialize(newRules, m_rulesFile.c_str());
|
||||
LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str());
|
||||
}
|
||||
catch (yaml::OperationException const& e) {
|
||||
LogError(LOG_HOST, "Cannot save the adjacent site map lookup file - %s (%s)", m_rulesFile.c_str(), e.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @defgroup lookups_asm Adjacent Site Map Lookups
|
||||
* @brief Implementation for adjacent site map lookup tables.
|
||||
* @ingroup lookups
|
||||
*
|
||||
* @file AdjSiteMapLookup.h
|
||||
* @ingroup lookups_asm
|
||||
* @file AdjSiteMapLookup.cpp
|
||||
* @ingroup lookups_asm
|
||||
*/
|
||||
#if !defined(__ADJ_SITE_MAP_LOOKUP_H__)
|
||||
#define __ADJ_SITE_MAP_LOOKUP_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/lookups/LookupTable.h"
|
||||
#include "common/yaml/Yaml.h"
|
||||
#include "common/Utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace lookups
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Represents an adjacent peer map entry.
|
||||
* @ingroup lookups_asm
|
||||
*/
|
||||
class HOST_SW_API AdjPeerMapEntry {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the AdjPeerMapEntry class.
|
||||
*/
|
||||
AdjPeerMapEntry() :
|
||||
m_active(false),
|
||||
m_peerId(0U),
|
||||
m_neighbors()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
/**
|
||||
* @brief Initializes a new instance of the AdjPeerMapEntry class.
|
||||
* @param node YAML node for this configuration block.
|
||||
*/
|
||||
AdjPeerMapEntry(yaml::Node& node) :
|
||||
AdjPeerMapEntry()
|
||||
{
|
||||
m_active = node["active"].as<bool>(false);
|
||||
m_peerId = node["peer_id"].as<uint32_t>(0U);
|
||||
|
||||
yaml::Node& neighborList = node["neighbors"];
|
||||
if (neighborList.size() > 0U) {
|
||||
for (size_t i = 0; i < neighborList.size(); i++) {
|
||||
uint32_t peerId = neighborList[i].as<uint32_t>(0U);
|
||||
m_neighbors.push_back(peerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equals operator. Copies this AdjPeerMapEntry to another AdjPeerMapEntry.
|
||||
* @param data Instance of AdjPeerMapEntry to copy.
|
||||
*/
|
||||
virtual AdjPeerMapEntry& operator= (const AdjPeerMapEntry& data)
|
||||
{
|
||||
if (this != &data) {
|
||||
m_active = data.m_active;
|
||||
m_peerId = data.m_peerId;
|
||||
m_neighbors = data.m_neighbors;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the count of neighbors.
|
||||
* @returns uint8_t Total count of peer neighbors.
|
||||
*/
|
||||
uint8_t neighborSize() const { return m_neighbors.size(); }
|
||||
|
||||
/**
|
||||
* @brief Helper to quickly determine if a entry is valid.
|
||||
* @returns bool True, if entry is valid, otherwise false.
|
||||
*/
|
||||
bool isEmpty() const
|
||||
{
|
||||
if (m_neighbors.size() > 0U)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the YAML structure for this TalkgroupRuleConfig.
|
||||
* @param[out] node YAML node.
|
||||
*/
|
||||
void getYaml(yaml::Node &node)
|
||||
{
|
||||
// We have to convert the bools back to strings to pass to the yaml node
|
||||
node["active"] = __BOOL_STR(m_active);
|
||||
node["peerId"] = __INT_STR(m_peerId);
|
||||
|
||||
// Get the lists
|
||||
yaml::Node neighborList;
|
||||
if (m_neighbors.size() > 0U) {
|
||||
for (auto neighbor : m_neighbors) {
|
||||
yaml::Node& newNeighbor = neighborList.push_back();
|
||||
newNeighbor = __INT_STR(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Flag indicating whether the rule is active.
|
||||
*/
|
||||
DECLARE_PROPERTY_PLAIN(bool, active);
|
||||
/**
|
||||
* @brief Peer ID.
|
||||
*/
|
||||
DECLARE_PROPERTY_PLAIN(uint32_t, peerId);
|
||||
|
||||
/**
|
||||
* @brief List of neighbor peers.
|
||||
*/
|
||||
DECLARE_PROPERTY_PLAIN(std::vector<uint32_t>, neighbors);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements a threading lookup table class that contains adjacent site map
|
||||
* information.
|
||||
* @ingroup lookups_asm
|
||||
*/
|
||||
class HOST_SW_API AdjSiteMapLookup : public Thread {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the AdjSiteMapLookup class.
|
||||
* @param filename Full-path to the routing rules file.
|
||||
* @param reloadTime Interval of time to reload the routing rules.
|
||||
*/
|
||||
AdjSiteMapLookup(const std::string& filename, uint32_t reloadTime);
|
||||
/**
|
||||
* @brief Finalizes a instance of the AdjSiteMapLookup class.
|
||||
*/
|
||||
~AdjSiteMapLookup() override;
|
||||
|
||||
/**
|
||||
* @brief Thread entry point. This function is provided to run the thread
|
||||
* for the lookup table.
|
||||
*/
|
||||
void entry() override;
|
||||
|
||||
/**
|
||||
* @brief Stops and unloads this lookup table.
|
||||
* @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping.
|
||||
*/
|
||||
void stop(bool noDestroy = false);
|
||||
/**
|
||||
* @brief Reads the lookup table from the specified lookup table file.
|
||||
* (NOTE: If the reload time for this lookup table is set to 0, a call to stop will also delete the object.)
|
||||
* @returns bool True, if lookup table was read, otherwise false.
|
||||
*/
|
||||
bool read();
|
||||
/**
|
||||
* @brief Reads the lookup table from the specified lookup table file.
|
||||
* @returns bool True, if lookup table was read, otherwise false.
|
||||
*/
|
||||
bool reload() { return load(); }
|
||||
/**
|
||||
* @brief Clears all entries from the lookup table.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Adds a new entry to the lookup table.
|
||||
* @param entry Peer map entry.
|
||||
*/
|
||||
void addEntry(AdjPeerMapEntry entry);
|
||||
/**
|
||||
* @brief Erases an existing entry from the lookup table by the specified unique ID.
|
||||
* @param id Unique ID to erase.
|
||||
*/
|
||||
void eraseEntry(uint32_t id);
|
||||
/**
|
||||
* @brief Finds a table entry in this lookup table.
|
||||
* @param id Unique identifier for table entry.
|
||||
* @returns AdjPeerMapEntry Table entry.
|
||||
*/
|
||||
virtual AdjPeerMapEntry find(uint32_t id);
|
||||
|
||||
/**
|
||||
* @brief Saves loaded talkgroup rules.
|
||||
*/
|
||||
bool commit();
|
||||
|
||||
/**
|
||||
* @brief Returns the filename used to load this lookup table.
|
||||
* @return std::string Full-path to the lookup table file.
|
||||
*/
|
||||
const std::string filename() { return m_rulesFile; };
|
||||
/**
|
||||
* @brief Sets the filename used to load this lookup table.
|
||||
* @param filename Full-path to the routing rules file.
|
||||
*/
|
||||
void filename(std::string filename) { m_rulesFile = filename; };
|
||||
|
||||
/**
|
||||
* @brief Helper to set the reload time of this lookup table.
|
||||
* @param reloadTime Lookup time in seconds.
|
||||
*/
|
||||
void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; }
|
||||
|
||||
private:
|
||||
std::string m_rulesFile;
|
||||
uint32_t m_reloadTime;
|
||||
yaml::Node m_rules;
|
||||
|
||||
bool m_stop;
|
||||
|
||||
static std::mutex m_mutex; //! Mutex used for change locking.
|
||||
static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used.
|
||||
|
||||
/**
|
||||
* @brief Loads the table from the passed lookup table file.
|
||||
* @return True, if lookup table was loaded, otherwise false.
|
||||
*/
|
||||
bool load();
|
||||
/**
|
||||
* @brief Saves the table to the passed lookup table file.
|
||||
* @return True, if lookup table was saved, otherwise false.
|
||||
*/
|
||||
bool save();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief List of adjacent site map entries.
|
||||
*/
|
||||
DECLARE_PROPERTY_PLAIN(std::vector<AdjPeerMapEntry>, adjPeerMap);
|
||||
};
|
||||
} // namespace lookups
|
||||
|
||||
#endif // __ADJ_SITE_MAP_LOOKUP_H__
|
||||
@ -1,108 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/P25Defines.h"
|
||||
#include "common/p25/dfsi/frames/MotPDUFrame.h"
|
||||
#include "common/p25/dfsi/DFSIDefines.h"
|
||||
#include "common/Utils.h"
|
||||
#include "common/Log.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
using namespace p25;
|
||||
using namespace p25::defines;
|
||||
using namespace p25::dfsi;
|
||||
using namespace p25::dfsi::defines;
|
||||
using namespace p25::dfsi::frames;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a instance of the MotPDUFrame class. */
|
||||
|
||||
MotPDUFrame::MotPDUFrame() :
|
||||
startOfStream(nullptr),
|
||||
pduHeaderData(nullptr)
|
||||
{
|
||||
pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES];
|
||||
::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES);
|
||||
|
||||
startOfStream = new MotStartOfStream();
|
||||
}
|
||||
|
||||
/* Initializes a instance of the MotPDUFrame class. */
|
||||
|
||||
MotPDUFrame::MotPDUFrame(uint8_t* data) :
|
||||
startOfStream(nullptr),
|
||||
pduHeaderData(nullptr)
|
||||
{
|
||||
pduHeaderData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES];
|
||||
::memset(pduHeaderData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES);
|
||||
|
||||
decode(data);
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the MotPDUFrame class. */
|
||||
|
||||
MotPDUFrame::~MotPDUFrame()
|
||||
{
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
if (pduHeaderData != nullptr)
|
||||
delete[] pduHeaderData;
|
||||
}
|
||||
|
||||
/* Decode a PDU frame. (only the PDU data header...) */
|
||||
|
||||
bool MotPDUFrame::decode(const uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
// create a new start of stream
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
startOfStream = new MotStartOfStream();
|
||||
|
||||
// create a buffer to decode the start record skipping the 10th byte (adjMM)
|
||||
uint8_t startBuffer[MotStartOfStream::LENGTH];
|
||||
::memset(startBuffer, 0x00U, MotStartOfStream::LENGTH);
|
||||
::memcpy(startBuffer + 1U, data, 4U);
|
||||
|
||||
// decode start of stream
|
||||
startOfStream->decode(startBuffer);
|
||||
|
||||
::memcpy(pduHeaderData, data + 9U, P25_PDU_HEADER_LENGTH_BYTES);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Encode a PDU frame. (only the PDU data header...) */
|
||||
|
||||
void MotPDUFrame::encode(uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
assert(startOfStream != nullptr);
|
||||
|
||||
// encode start of stream - scope is intentional
|
||||
{
|
||||
uint8_t buffer[MotStartOfStream::LENGTH];
|
||||
startOfStream->encode(buffer);
|
||||
|
||||
// copy to data array (skipping first and last bytes)
|
||||
::memcpy(data + 1U, buffer + 1U, 4U);
|
||||
}
|
||||
|
||||
// encode PDU - scope is intentional
|
||||
{
|
||||
data[0U] = DFSIFrameType::PDU;
|
||||
::memcpy(data + 9U, pduHeaderData, P25_PDU_HEADER_LENGTH_BYTES);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/P25Defines.h"
|
||||
#include "common/p25/dfsi/frames/MotTDULCFrame.h"
|
||||
#include "common/p25/dfsi/DFSIDefines.h"
|
||||
#include "common/Utils.h"
|
||||
#include "common/Log.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
using namespace p25;
|
||||
using namespace p25::defines;
|
||||
using namespace p25::dfsi;
|
||||
using namespace p25::dfsi::defines;
|
||||
using namespace p25::dfsi::frames;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a instance of the MotTDULCFrame class. */
|
||||
|
||||
MotTDULCFrame::MotTDULCFrame() :
|
||||
startOfStream(nullptr),
|
||||
tdulcData(nullptr)
|
||||
{
|
||||
tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES];
|
||||
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
|
||||
|
||||
startOfStream = new MotStartOfStream();
|
||||
}
|
||||
|
||||
/* Initializes a instance of the MotTDULCFrame class. */
|
||||
|
||||
MotTDULCFrame::MotTDULCFrame(uint8_t* data) :
|
||||
startOfStream(nullptr),
|
||||
tdulcData(nullptr)
|
||||
{
|
||||
tdulcData = new uint8_t[P25_TDULC_FRAME_LENGTH_BYTES];
|
||||
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
|
||||
|
||||
decode(data);
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the MotTDULCFrame class. */
|
||||
|
||||
MotTDULCFrame::~MotTDULCFrame()
|
||||
{
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
if (tdulcData != nullptr)
|
||||
delete[] tdulcData;
|
||||
}
|
||||
|
||||
/* Decode a TDULC frame. */
|
||||
|
||||
bool MotTDULCFrame::decode(const uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
// create a new start of stream
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
startOfStream = new MotStartOfStream();
|
||||
|
||||
// create a buffer to decode the start record
|
||||
uint8_t startBuffer[DFSI_MOT_START_LEN];
|
||||
::memset(startBuffer, 0x00U, DFSI_MOT_START_LEN);
|
||||
::memcpy(startBuffer + 1U, data, DFSI_MOT_START_LEN - 1U);
|
||||
|
||||
// decode start of stream
|
||||
startOfStream->decode(startBuffer);
|
||||
|
||||
uint8_t tdulcBuffer[9U];
|
||||
::memcpy(tdulcBuffer, data + DFSI_MOT_START_LEN, 9U);
|
||||
|
||||
::memset(tdulcData, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES);
|
||||
tdulcData[0U] = (uint8_t)((tdulcBuffer[0U] >> 3) & 0x3FU);
|
||||
tdulcData[1U] = (uint8_t)(((tdulcBuffer[0U] & 0x07U) << 3) | ((tdulcBuffer[1U] >> 4) & 0x07U));
|
||||
tdulcData[2U] = (uint8_t)(((tdulcBuffer[1U] & 0x0FU) << 2) | ((tdulcBuffer[2U] >> 5) & 0x03U));
|
||||
tdulcData[3U] = (uint8_t)(tdulcBuffer[2U] & 0x1FU);
|
||||
tdulcData[4U] = (uint8_t)((tdulcBuffer[3U] >> 3) & 0x3FU);
|
||||
tdulcData[5U] = (uint8_t)(((tdulcBuffer[3U] & 0x07U) << 3) | ((tdulcBuffer[4U] >> 4) & 0x07U));
|
||||
tdulcData[6U] = (uint8_t)(((tdulcBuffer[4U] & 0x0FU) << 2) | ((tdulcBuffer[5U] >> 5) & 0x03U));
|
||||
tdulcData[7U] = (uint8_t)(tdulcBuffer[5U] & 0x1FU);
|
||||
tdulcData[8U] = (uint8_t)((tdulcBuffer[6U] >> 3) & 0x3FU);
|
||||
tdulcData[9U] = (uint8_t)(((tdulcBuffer[6U] & 0x07U) << 3) | ((tdulcBuffer[7U] >> 4) & 0x07U));
|
||||
tdulcData[10U] = (uint8_t)(((tdulcBuffer[7U] & 0x0FU) << 2) | ((tdulcBuffer[8U] >> 5) & 0x03U));
|
||||
tdulcData[11U] = (uint8_t)(tdulcBuffer[8U] & 0x1FU);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Encode a TDULC frame. */
|
||||
|
||||
void MotTDULCFrame::encode(uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
assert(startOfStream != nullptr);
|
||||
|
||||
// encode start of stream - scope is intentional
|
||||
{
|
||||
uint8_t buffer[DFSI_MOT_START_LEN];
|
||||
startOfStream->encode(buffer);
|
||||
|
||||
// copy to data array
|
||||
::memcpy(data + 1U, buffer + 1U, DFSI_MOT_START_LEN - 1U);
|
||||
}
|
||||
|
||||
// encode TDULC - scope is intentional
|
||||
{
|
||||
data[0U] = DFSIFrameType::MOT_TDULC;
|
||||
|
||||
data[DFSI_MOT_START_LEN + 1U] = (uint8_t)((tdulcData[0U] & 0x3FU) << 3) | ((tdulcData[1U] >> 3) & 0x07U);
|
||||
data[DFSI_MOT_START_LEN + 2U] = (uint8_t)((tdulcData[1U] & 0x0FU) << 4) | ((tdulcData[2U] >> 2) & 0x0FU);
|
||||
data[DFSI_MOT_START_LEN + 3U] = (uint8_t)((tdulcData[2U] & 0x03U)) | (tdulcData[3U] & 0x3FU);
|
||||
|
||||
data[DFSI_MOT_START_LEN + 4U] = (uint8_t)((tdulcData[4U] & 0x3FU) << 3) | ((tdulcData[5U] >> 3) & 0x07U);
|
||||
data[DFSI_MOT_START_LEN + 5U] = (uint8_t)((tdulcData[5U] & 0x0FU) << 4) | ((tdulcData[6U] >> 2) & 0x0FU);
|
||||
data[DFSI_MOT_START_LEN + 6U] = (uint8_t)((tdulcData[6U] & 0x03U)) | (tdulcData[7U] & 0x3FU);
|
||||
|
||||
data[DFSI_MOT_START_LEN + 7U] = (uint8_t)((tdulcData[8U] & 0x3FU) << 3) | ((tdulcData[9U] >> 3) & 0x07U);
|
||||
data[DFSI_MOT_START_LEN + 8U] = (uint8_t)((tdulcData[9U] & 0x0FU) << 4) | ((tdulcData[10U] >> 2) & 0x0FU);
|
||||
data[DFSI_MOT_START_LEN + 9U] = (uint8_t)((tdulcData[10U] & 0x03U)) | (tdulcData[11U] & 0x3FU);
|
||||
|
||||
data[DFSI_MOT_START_LEN + 11U] = DFSI_BUSY_BITS_IDLE;
|
||||
}
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2024 Patrick McDonnell, W3AXL
|
||||
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/dfsi/frames/MotVoiceHeader1.h"
|
||||
#include "common/p25/dfsi/DFSIDefines.h"
|
||||
#include "common/Utils.h"
|
||||
#include "common/Log.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
using namespace p25;
|
||||
using namespace p25::dfsi;
|
||||
using namespace p25::dfsi::defines;
|
||||
using namespace p25::dfsi::frames;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a instance of the MotVoiceHeader1 class. */
|
||||
|
||||
MotVoiceHeader1::MotVoiceHeader1() :
|
||||
header(nullptr),
|
||||
startOfStream(nullptr),
|
||||
m_icw(ICWFlag::DIU),
|
||||
m_rssi(0U),
|
||||
m_rssiValidity(RssiValidityFlag::INVALID),
|
||||
m_nRssi(0U)
|
||||
{
|
||||
startOfStream = new MotStartOfStream();
|
||||
|
||||
header = new uint8_t[HCW_LENGTH];
|
||||
::memset(header, 0x00U, HCW_LENGTH);
|
||||
}
|
||||
|
||||
/* Initializes a instance of the MotVoiceHeader1 class. */
|
||||
|
||||
MotVoiceHeader1::MotVoiceHeader1(uint8_t* data) :
|
||||
header(nullptr),
|
||||
startOfStream(nullptr),
|
||||
m_icw(ICWFlag::DIU),
|
||||
m_rssi(0U),
|
||||
m_rssiValidity(RssiValidityFlag::INVALID),
|
||||
m_nRssi(0U)
|
||||
{
|
||||
decode(data);
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the MotVoiceHeader1 class. */
|
||||
|
||||
MotVoiceHeader1::~MotVoiceHeader1()
|
||||
{
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
if (header != nullptr)
|
||||
delete[] header;
|
||||
}
|
||||
|
||||
/* Decode a voice header 1 frame. */
|
||||
|
||||
bool MotVoiceHeader1::decode(const uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
// create a start of stream
|
||||
if (startOfStream != nullptr)
|
||||
delete startOfStream;
|
||||
startOfStream = new MotStartOfStream();
|
||||
|
||||
uint8_t buffer[MotStartOfStream::LENGTH];
|
||||
::memset(buffer, 0x00U, MotStartOfStream::LENGTH);
|
||||
|
||||
// we copy the bytes from [1:4]
|
||||
::memcpy(buffer + 1U, data + 1U, 4);
|
||||
startOfStream->decode(buffer);
|
||||
|
||||
// decode the other stuff
|
||||
m_icw = (ICWFlag::E)data[5U];
|
||||
m_rssi = data[6U];
|
||||
m_rssiValidity = (RssiValidityFlag::E)data[7U];
|
||||
m_nRssi = data[8U];
|
||||
|
||||
// our header includes the trailing source and check bytes
|
||||
if (header != nullptr)
|
||||
delete[] header;
|
||||
header = new uint8_t[HCW_LENGTH];
|
||||
::memset(header, 0x00U, HCW_LENGTH);
|
||||
::memcpy(header, data + 9U, HCW_LENGTH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Encode a voice header 1 frame. */
|
||||
|
||||
void MotVoiceHeader1::encode(uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
assert(startOfStream != nullptr);
|
||||
|
||||
data[0U] = DFSIFrameType::MOT_VHDR_1;
|
||||
|
||||
// scope is intentional
|
||||
{
|
||||
uint8_t buffer[MotStartOfStream::LENGTH];
|
||||
::memset(buffer, 0x00U, MotStartOfStream::LENGTH);
|
||||
startOfStream->encode(buffer);
|
||||
|
||||
// copy the 4 start record bytes from the start of stream frame
|
||||
::memcpy(data + 1U, buffer + 1U, 4U);
|
||||
}
|
||||
|
||||
data[5U] = m_icw;
|
||||
data[6U] = m_rssi;
|
||||
data[7U] = m_rssiValidity;
|
||||
data[8U] = m_nRssi;
|
||||
|
||||
// our header includes the trailing source and check bytes
|
||||
if (header != nullptr) {
|
||||
::memcpy(data + 9U, header, HCW_LENGTH);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue