From b2586654a251e154eb12d3d866641175b73ebf67 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Thu, 11 Jan 2018 19:35:33 +0000 Subject: [PATCH] Initial commit of NXDN scaffolding. --- Conf.cpp | 67 +- Conf.h | 19 +- Defines.h | 3 +- MMDVM.ini | 8 + MMDVMHost.cpp | 88 ++- MMDVMHost.h | 5 +- MMDVMHost.vcxproj | 3 + MMDVMHost.vcxproj.filters | 9 + Makefile | 6 +- Makefile.Pi | 6 +- Makefile.Pi.Adafruit | 4 +- Makefile.Pi.HD44780 | 4 +- Makefile.Pi.OLED | 4 +- Makefile.Pi.PCF8574 | 4 +- Makefile.Solaris | 6 +- Modem.cpp | 119 +++- Modem.h | 14 +- NXDNControl.cpp | 1244 +++++++++++++++++++++++++++++++++++++ NXDNControl.h | 110 ++++ NXDNDefines.h | 35 ++ 20 files changed, 1722 insertions(+), 36 deletions(-) create mode 100644 NXDNControl.cpp create mode 100644 NXDNControl.h create mode 100644 NXDNDefines.h diff --git a/Conf.cpp b/Conf.cpp index 64139fc..a33c32c 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * * 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 @@ -40,10 +40,12 @@ enum SECTION { SECTION_DMR, SECTION_FUSION, SECTION_P25, + SECTION_NXDN, SECTION_DSTAR_NETWORK, SECTION_DMR_NETWORK, SECTION_FUSION_NETWORK, SECTION_P25_NETWORK, + SECTION_NXDN_NETWORK, SECTION_TFTSERIAL, SECTION_HD44780, SECTION_NEXTION, @@ -95,6 +97,7 @@ m_modemDStarTXLevel(50.0F), m_modemDMRTXLevel(50.0F), m_modemYSFTXLevel(50.0F), m_modemP25TXLevel(50.0F), +m_modemNXDNTXLevel(50.0F), m_modemRSSIMappingFile(), m_modemTrace(false), m_modemDebug(false), @@ -138,6 +141,12 @@ m_p25SelfOnly(false), m_p25OverrideUID(false), m_p25RemoteGateway(false), m_p25ModeHang(10U), +m_nxdnEnabled(false), +m_nxdnId(0U), +m_nxdnRAN(13U), +m_nxdnSelfOnly(false), +m_nxdnRemoteGateway(false), +m_nxdnModeHang(10U), m_dstarNetworkEnabled(false), m_dstarGatewayAddress(), m_dstarGatewayPort(0U), @@ -240,6 +249,8 @@ bool CConf::read() section = SECTION_FUSION; else if (::strncmp(buffer, "[P25]", 5U) == 0) section = SECTION_P25; + else if (::strncmp(buffer, "[NXDN]", 6U) == 0) + section = SECTION_NXDN; else if (::strncmp(buffer, "[D-Star Network]", 16U) == 0) section = SECTION_DSTAR_NETWORK; else if (::strncmp(buffer, "[DMR Network]", 13U) == 0) @@ -248,6 +259,8 @@ bool CConf::read() section = SECTION_FUSION_NETWORK; else if (::strncmp(buffer, "[P25 Network]", 13U) == 0) section = SECTION_P25_NETWORK; + else if (::strncmp(buffer, "[NXDN Network]", 14U) == 0) + section = SECTION_NXDN_NETWORK; else if (::strncmp(buffer, "[TFT Serial]", 12U) == 0) section = SECTION_TFTSERIAL; else if (::strncmp(buffer, "[HD44780]", 9U) == 0) @@ -365,7 +378,7 @@ bool CConf::read() else if (::strcmp(key, "RXLevel") == 0) m_modemRXLevel = float(::atof(value)); else if (::strcmp(key, "TXLevel") == 0) - m_modemCWIdTXLevel = m_modemDStarTXLevel = m_modemDMRTXLevel = m_modemYSFTXLevel = m_modemP25TXLevel = float(::atof(value)); + m_modemCWIdTXLevel = m_modemDStarTXLevel = m_modemDMRTXLevel = m_modemYSFTXLevel = m_modemP25TXLevel = m_modemNXDNTXLevel = float(::atof(value)); else if (::strcmp(key, "CWIdTXLevel") == 0) m_modemCWIdTXLevel = float(::atof(value)); else if (::strcmp(key, "D-StarTXLevel") == 0) @@ -376,6 +389,8 @@ bool CConf::read() m_modemYSFTXLevel = float(::atof(value)); else if (::strcmp(key, "P25TXLevel") == 0) m_modemP25TXLevel = float(::atof(value)); + else if (::strcmp(key, "NXDNTXLevel") == 0) + m_modemNXDNTXLevel = float(::atof(value)); else if (::strcmp(key, "RSSIMappingFile") == 0) m_modemRSSIMappingFile = value; else if (::strcmp(key, "Trace") == 0) @@ -509,6 +524,19 @@ bool CConf::read() m_p25RemoteGateway = ::atoi(value) == 1; else if (::strcmp(key, "ModeHang") == 0) m_p25ModeHang = (unsigned int)::atoi(value); + } else if (section == SECTION_NXDN) { + if (::strcmp(key, "Enable") == 0) + m_nxdnEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Id") == 0) + m_nxdnId = (unsigned int)::atoi(value); + else if (::strcmp(key, "RAN") == 0) + m_nxdnRAN = (unsigned int)::atoi(value); + else if (::strcmp(key, "SelfOnly") == 0) + m_nxdnSelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "RemoteGateway") == 0) + m_nxdnRemoteGateway = ::atoi(value) == 1; + else if (::strcmp(key, "ModeHang") == 0) + m_nxdnModeHang = (unsigned int)::atoi(value); } else if (section == SECTION_DSTAR_NETWORK) { if (::strcmp(key, "Enable") == 0) m_dstarNetworkEnabled = ::atoi(value) == 1; @@ -853,6 +881,11 @@ float CConf::getModemP25TXLevel() const return m_modemP25TXLevel; } +float CConf::getModemNXDNTXLevel() const +{ + return m_modemNXDNTXLevel; +} + std::string CConf::getModemRSSIMappingFile () const { return m_modemRSSIMappingFile; @@ -1068,6 +1101,36 @@ unsigned int CConf::getP25ModeHang() const return m_p25ModeHang; } +bool CConf::getNXDNEnabled() const +{ + return m_nxdnEnabled; +} + +unsigned int CConf::getNXDNId() const +{ + return m_nxdnId; +} + +unsigned int CConf::getNXDNRAN() const +{ + return m_nxdnRAN; +} + +bool CConf::getNXDNSelfOnly() const +{ + return m_nxdnSelfOnly; +} + +bool CConf::getNXDNRemoteGateway() const +{ + return m_nxdnRemoteGateway; +} + +unsigned int CConf::getNXDNModeHang() const +{ + return m_nxdnModeHang; +} + bool CConf::getDStarNetworkEnabled() const { return m_dstarNetworkEnabled; diff --git a/Conf.h b/Conf.h index ee48c99..5f1049d 100644 --- a/Conf.h +++ b/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * * 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 @@ -82,6 +82,7 @@ public: float getModemDMRTXLevel() const; float getModemYSFTXLevel() const; float getModemP25TXLevel() const; + float getModemNXDNTXLevel() const; std::string getModemRSSIMappingFile() const; bool getModemTrace() const; bool getModemDebug() const; @@ -136,6 +137,14 @@ public: bool getP25RemoteGateway() const; unsigned int getP25ModeHang() const; + // The NXDN section + bool getNXDNEnabled() const; + unsigned int getNXDNId() const; + unsigned int getNXDNRAN() const; + bool getNXDNSelfOnly() const; + bool getNXDNRemoteGateway() const; + unsigned int getNXDNModeHang() const; + // The D-Star Network section bool getDStarNetworkEnabled() const; std::string getDStarGatewayAddress() const; @@ -260,6 +269,7 @@ private: float m_modemDMRTXLevel; float m_modemYSFTXLevel; float m_modemP25TXLevel; + float m_modemNXDNTXLevel; std::string m_modemRSSIMappingFile; bool m_modemTrace; bool m_modemDebug; @@ -309,6 +319,13 @@ private: bool m_p25RemoteGateway; unsigned int m_p25ModeHang; + bool m_nxdnEnabled; + unsigned int m_nxdnId; + unsigned int m_nxdnRAN; + bool m_nxdnSelfOnly; + bool m_nxdnRemoteGateway; + unsigned int m_nxdnModeHang; + bool m_dstarNetworkEnabled; std::string m_dstarGatewayAddress; unsigned int m_dstarGatewayPort; diff --git a/Defines.h b/Defines.h index 9285789..ebfcd83 100644 --- a/Defines.h +++ b/Defines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * * 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 @@ -24,6 +24,7 @@ const unsigned char MODE_DSTAR = 1U; const unsigned char MODE_DMR = 2U; const unsigned char MODE_YSF = 3U; const unsigned char MODE_P25 = 4U; +const unsigned char MODE_NXDN = 5U; const unsigned char MODE_CW = 98U; const unsigned char MODE_LOCKOUT = 99U; const unsigned char MODE_ERROR = 100U; diff --git a/MMDVM.ini b/MMDVM.ini index 16f9ec9..ef52eed 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -56,6 +56,7 @@ RFLevel=100 # DMRTXLevel=50 # YSFTXLevel=50 # P25TXLevel=50 +# NXDNTXLevel=50 RSSIMappingFile=RSSI.dat Trace=0 Debug=0 @@ -105,6 +106,13 @@ OverrideUIDCheck=0 RemoteGateway=0 # ModeHang=10 +[NXDN] +Enable=1 +RAN=13 +SelfOnly=0 +RemoteGateway=0 +# ModeHang=10 + [D-Star Network] Enable=1 GatewayAddress=127.0.0.1 diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index 61f3e73..c482d90 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -29,6 +29,7 @@ #include "NullDisplay.h" #include "YSFControl.h" #include "P25Control.h" +#include "NXDNControl.h" #include "Nextion.h" #include "LCDproc.h" #include "Thread.h" @@ -74,7 +75,7 @@ static void sigHandler(int signum) const char* HEADER1 = "This software is for use on amateur radio networks only,"; const char* HEADER2 = "it is to be used for educational purposes only. Its use on"; const char* HEADER3 = "commercial networks is strictly prohibited."; -const char* HEADER4 = "Copyright(C) 2015-2017 by Jonathan Naylor, G4KLX and others"; +const char* HEADER4 = "Copyright(C) 2015-2018 by Jonathan Naylor, G4KLX and others"; int main(int argc, char** argv) { @@ -139,10 +140,12 @@ m_dstarRFModeHang(10U), m_dmrRFModeHang(10U), m_ysfRFModeHang(10U), m_p25RFModeHang(10U), +m_nxdnRFModeHang(10U), m_dstarNetModeHang(3U), m_dmrNetModeHang(3U), m_ysfNetModeHang(3U), m_p25NetModeHang(3U), +m_nxdnNetModeHang(3U), m_modeTimer(1000U), m_dmrTXTimer(1000U), m_cwIdTimer(1000U), @@ -152,6 +155,7 @@ m_dstarEnabled(false), m_dmrEnabled(false), m_ysfEnabled(false), m_p25Enabled(false), +m_nxdnEnabled(false), m_cwIdTime(0U), m_lookup(NULL), m_callsign(), @@ -461,6 +465,24 @@ int CMMDVMHost::run() p25 = new CP25Control(nac, id, selfOnly, uidOverride, m_p25Network, m_display, m_timeout, m_duplex, m_lookup, remoteGateway, rssi); } + CNXDNControl* nxdn = NULL; + if (m_nxdnEnabled) { + unsigned int id = m_conf.getNXDNId(); + unsigned int ran = m_conf.getNXDNRAN(); + bool selfOnly = m_conf.getNXDNSelfOnly(); + bool remoteGateway = m_conf.getNXDNRemoteGateway(); + m_nxdnRFModeHang = m_conf.getNXDNModeHang(); + + LogInfo("NXDN RF Parameters"); + LogInfo(" Id: %u", id); + LogInfo(" RAN: %u", ran); + LogInfo(" Self Only: %s", selfOnly ? "yes" : "no"); + LogInfo(" Remote Gateway: %s", remoteGateway ? "yes" : "no"); + LogInfo(" Mode Hang: %us", m_p25RFModeHang); + + nxdn = new CNXDNControl(ran, id, selfOnly, NULL, m_display, m_timeout, m_duplex, m_lookup, remoteGateway, rssi); + } + setMode(MODE_IDLE); LogMessage("MMDVMHost-%s is running", VERSION); @@ -614,6 +636,22 @@ int CMMDVMHost::run() } } + len = m_modem->readNXDNData(data); + if (nxdn != NULL && len > 0U) { + if (m_mode == MODE_IDLE) { + bool ret = nxdn->writeModem(data, len); + if (ret) { + m_modeTimer.setTimeout(m_nxdnRFModeHang); + setMode(MODE_NXDN); + } + } else if (m_mode == MODE_NXDN) { + nxdn->writeModem(data, len); + m_modeTimer.start(); + } else if (m_mode != MODE_LOCKOUT) { + LogWarning("NXDN modem data received when in mode %u", m_mode); + } + } + if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) setMode(MODE_IDLE); @@ -720,6 +758,26 @@ int CMMDVMHost::run() } } + if (nxdn != NULL) { + ret = m_modem->hasNXDNSpace(); + if (ret) { + len = nxdn->readModem(data); + if (len > 0U) { + if (m_mode == MODE_IDLE) { + m_modeTimer.setTimeout(m_nxdnNetModeHang); + setMode(MODE_NXDN); + } + if (m_mode == MODE_NXDN) { + m_modem->writeNXDNData(data, len); + m_modeTimer.start(); + } + else if (m_mode != MODE_LOCKOUT) { + LogWarning("NXDN data received when in mode %u", m_mode); + } + } + } + } + if (m_dmrNetwork != NULL) { bool run = m_dmrNetwork->wantsBeacon(); if (dmrBeaconsEnabled && run && m_mode == MODE_IDLE && !m_modem->hasTX()) { @@ -744,6 +802,8 @@ int CMMDVMHost::run() ysf->clock(ms); if (p25 != NULL) p25->clock(ms); + if (nxdn != NULL) + nxdn->clock(ms); if (m_dstarNetwork != NULL) m_dstarNetwork->clock(ms); @@ -825,6 +885,7 @@ int CMMDVMHost::run() delete dmr; delete ysf; delete p25; + delete nxdn; return 0; } @@ -843,6 +904,7 @@ bool CMMDVMHost::createModem() float dmrTXLevel = m_conf.getModemDMRTXLevel(); float ysfTXLevel = m_conf.getModemYSFTXLevel(); float p25TXLevel = m_conf.getModemP25TXLevel(); + float nxdnTXLevel = m_conf.getModemNXDNTXLevel(); bool trace = m_conf.getModemTrace(); bool debug = m_conf.getModemDebug(); unsigned int colorCode = m_conf.getDMRColorCode(); @@ -873,12 +935,13 @@ bool CMMDVMHost::createModem() LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel); LogInfo(" YSF TX Level: %.1f%%", ysfTXLevel); LogInfo(" P25 TX Level: %.1f%%", p25TXLevel); + LogInfo(" NXDN TX Level: %.1f%%", nxdnTXLevel); LogInfo(" RX Frequency: %uHz (%uHz)", rxFrequency, rxFrequency + rxOffset); LogInfo(" TX Frequency: %uHz (%uHz)", txFrequency, txFrequency + txOffset); m_modem = new CModem(port, m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, trace, debug); - m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled); - m_modem->setLevels(rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel); + m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEabled); + m_modem->setLevels(rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel); m_modem->setRFParams(rxFrequency, rxOffset, txFrequency, txOffset, txDCOffset, rxDCOffset, rfLevel); m_modem->setDMRParams(colorCode); m_modem->setYSFParams(lowDeviation); @@ -1056,6 +1119,7 @@ void CMMDVMHost::readParams() m_dmrEnabled = m_conf.getDMREnabled(); m_ysfEnabled = m_conf.getFusionEnabled(); m_p25Enabled = m_conf.getP25Enabled(); + m_nxdnEnabled = m_conf.getNXDNEnabled(); m_duplex = m_conf.getDuplex(); m_callsign = m_conf.getCallsign(); m_id = m_conf.getId(); @@ -1070,6 +1134,7 @@ void CMMDVMHost::readParams() LogInfo(" DMR: %s", m_dmrEnabled ? "enabled" : "disabled"); LogInfo(" YSF: %s", m_ysfEnabled ? "enabled" : "disabled"); LogInfo(" P25: %s", m_p25Enabled ? "enabled" : "disabled"); + LogInfo(" NXDN: %s", m_nxdnEnabled ? "enabled" : "disabled"); } void CMMDVMHost::createDisplay() @@ -1296,6 +1361,23 @@ void CMMDVMHost::setMode(unsigned char mode) m_cwIdTimer.stop(); break; + case MODE_NXDN: + if (m_dstarNetwork != NULL) + m_dstarNetwork->enable(false); + if (m_dmrNetwork != NULL) + m_dmrNetwork->enable(false); + if (m_ysfNetwork != NULL) + m_ysfNetwork->enable(false); + if (m_p25Network != NULL) + m_p25Network->enable(false); + m_modem->setMode(MODE_NXDN); + if (m_ump != NULL) + m_ump->setMode(MODE_NXDN); + m_mode = MODE_NXDN; + m_modeTimer.start(); + m_cwIdTimer.stop(); + break; + case MODE_LOCKOUT: LogMessage("Mode set to Lockout"); if (m_dstarNetwork != NULL) diff --git a/MMDVMHost.h b/MMDVMHost.h index 190a805..8c5b3a2 100644 --- a/MMDVMHost.h +++ b/MMDVMHost.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * * 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 @@ -54,10 +54,12 @@ private: unsigned int m_dmrRFModeHang; unsigned int m_ysfRFModeHang; unsigned int m_p25RFModeHang; + unsigned int m_nxdnRFModeHang; unsigned int m_dstarNetModeHang; unsigned int m_dmrNetModeHang; unsigned int m_ysfNetModeHang; unsigned int m_p25NetModeHang; + unsigned int m_nxdnNetModeHang; CTimer m_modeTimer; CTimer m_dmrTXTimer; CTimer m_cwIdTimer; @@ -67,6 +69,7 @@ private: bool m_dmrEnabled; bool m_ysfEnabled; bool m_p25Enabled; + bool m_nxdnEnabled; unsigned int m_cwIdTime; CDMRLookup* m_lookup; std::string m_callsign; diff --git a/MMDVMHost.vcxproj b/MMDVMHost.vcxproj index 62fd668..9a73755 100644 --- a/MMDVMHost.vcxproj +++ b/MMDVMHost.vcxproj @@ -193,6 +193,8 @@ + + @@ -265,6 +267,7 @@ + diff --git a/MMDVMHost.vcxproj.filters b/MMDVMHost.vcxproj.filters index d6ba4f3..49d3d1e 100644 --- a/MMDVMHost.vcxproj.filters +++ b/MMDVMHost.vcxproj.filters @@ -230,6 +230,12 @@ Header Files + + Header Files + + + Header Files + @@ -430,5 +436,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Makefile b/Makefile index 1a45e69..42ccbfe 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ LDFLAGS = -g OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o SHA256.o \ - StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o P25Audio.o \ + P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o \ + SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Pi b/Makefile.Pi index f28cda0..2d4c1a2 100644 --- a/Makefile.Pi +++ b/Makefile.Pi @@ -9,9 +9,9 @@ LDFLAGS = -g -L/usr/local/lib OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o SHA256.o \ - StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o P25Audio.o \ + P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o \ + SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Pi.Adafruit b/Makefile.Pi.Adafruit index 9f69fee..5121a6f 100644 --- a/Makefile.Pi.Adafruit +++ b/Makefile.Pi.Adafruit @@ -9,8 +9,8 @@ LDFLAGS = -g -L/usr/local/lib OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o \ - P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ + Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o \ + P25Audio.o P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ SerialPort.o SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Pi.HD44780 b/Makefile.Pi.HD44780 index 4548ee4..0c5fc11 100644 --- a/Makefile.Pi.HD44780 +++ b/Makefile.Pi.HD44780 @@ -9,8 +9,8 @@ LDFLAGS = -g -L/usr/local/lib OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o \ - P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ + Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o \ + P25Audio.o P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ SerialPort.o SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Pi.OLED b/Makefile.Pi.OLED index 1845bc2..36b223c 100644 --- a/Makefile.Pi.OLED +++ b/Makefile.Pi.OLED @@ -9,8 +9,8 @@ LDFLAGS = -g -L/usr/local/lib OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o JitterBuffer.o OLED.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o \ - P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ + Golay24128.o Hamming.o JitterBuffer.o OLED.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o \ + P25Audio.o P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ SerialPort.o SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Pi.PCF8574 b/Makefile.Pi.PCF8574 index d1fa047..3942c66 100644 --- a/Makefile.Pi.PCF8574 +++ b/Makefile.Pi.PCF8574 @@ -9,8 +9,8 @@ LDFLAGS = -g -L/usr/local/lib OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o \ - P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ + Golay24128.o Hamming.o HD44780.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o \ + P25Audio.o P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o \ SerialPort.o SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Makefile.Solaris b/Makefile.Solaris index 9e8769f..1275515 100644 --- a/Makefile.Solaris +++ b/Makefile.Solaris @@ -9,9 +9,9 @@ LDFLAGS = -g OBJECTS = \ AMBEFEC.o BCH.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRLookup.o DMRLC.o \ DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o \ - Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o SHA256.o \ - StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + Golay24128.o Hamming.o JitterBuffer.o LCDproc.o Log.o MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NXDNControl.o P25Audio.o \ + P25Control.o P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o QR1676.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialPort.o \ + SHA256.o StopWatch.o Sync.o TFTSerial.o Thread.o Timer.o UDPSocket.o UMP.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost diff --git a/Modem.cpp b/Modem.cpp index c6bab00..097ec80 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2011-2018 by Jonathan Naylor G4KLX * * 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 @@ -20,6 +20,7 @@ #include "DMRDefines.h" #include "YSFDefines.h" #include "P25Defines.h" +#include "NXDNDefines.h" #include "Thread.h" #include "Modem.h" #include "Utils.h" @@ -67,6 +68,9 @@ const unsigned char MMDVM_P25_HDR = 0x30U; const unsigned char MMDVM_P25_LDU = 0x31U; const unsigned char MMDVM_P25_LOST = 0x32U; +const unsigned char MMDVM_NXDN_DATA = 0x40U; +const unsigned char MMDVM_NXDN_LOST = 0x41U; + const unsigned char MMDVM_ACK = 0x70U; const unsigned char MMDVM_NAK = 0x7FU; @@ -99,6 +103,7 @@ m_dstarTXLevel(0U), m_dmrTXLevel(0U), m_ysfTXLevel(0U), m_p25TXLevel(0U), +m_nxdnTXLevel(0U), m_trace(trace), m_debug(debug), m_rxFrequency(0U), @@ -107,6 +112,7 @@ m_dstarEnabled(false), m_dmrEnabled(false), m_ysfEnabled(false), m_p25Enabled(false), +m_nxdnEnabled(false), m_rxDCOffset(0), m_txDCOffset(0), m_serial(port, SERIAL_115200, true), @@ -123,6 +129,8 @@ m_rxYSFData(1000U, "Modem RX YSF"), m_txYSFData(1000U, "Modem TX YSF"), m_rxP25Data(1000U, "Modem RX P25"), m_txP25Data(1000U, "Modem TX P25"), +m_rxNXDNData(1000U, "Modem RX NXDN"), +m_txNXDNData(1000U, "Modem TX NXDN"), m_statusTimer(1000U, 0U, 250U), m_inactivityTimer(1000U, 2U), m_playoutTimer(1000U, 0U, 10U), @@ -131,6 +139,7 @@ m_dmrSpace1(0U), m_dmrSpace2(0U), m_ysfSpace(0U), m_p25Space(0U), +m_nxdnSpace(0U), m_tx(false), m_cd(false), m_lockout(false), @@ -153,18 +162,19 @@ void CModem::setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int tx m_txFrequency = txFrequency + txOffset; m_txDCOffset = txDCOffset; m_rxDCOffset = rxDCOffset; - m_rfLevel = rfLevel; + m_rfLevel = rfLevel; } -void CModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled) +void CModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled) { m_dstarEnabled = dstarEnabled; m_dmrEnabled = dmrEnabled; m_ysfEnabled = ysfEnabled; m_p25Enabled = p25Enabled; + m_nxdnEnabled = nxdnEnabled; } -void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel) +void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel) { m_rxLevel = rxLevel; m_cwIdTXLevel = cwIdTXLevel; @@ -172,6 +182,7 @@ void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, flo m_dmrTXLevel = dmrTXLevel; m_ysfTXLevel = ysfTXLevel; m_p25TXLevel = p25TXLevel; + m_nxdnTXLevel = nxdnTXLevel; } void CModem::setDMRParams(unsigned int colorCode) @@ -429,6 +440,32 @@ void CModem::clock(unsigned int ms) } break; + case MMDVM_NXDN_DATA: { + if (m_trace) + CUtils::dump(1U, "RX NXDN Data", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxNXDNData.addData(&data, 1U); + + data = TAG_DATA; + m_rxNXDNData.addData(&data, 1U); + + m_rxNXDNData.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_NXDN_LOST: { + if (m_trace) + CUtils::dump(1U, "RX NXDN Lost", m_buffer, m_length); + + unsigned char data = 1U; + m_rxNXDNData.addData(&data, 1U); + + data = TAG_LOST; + m_rxNXDNData.addData(&data, 1U); + } + break; + case MMDVM_GET_STATUS: { // if (m_trace) // CUtils::dump(1U, "GET_STATUS", m_buffer, m_length); @@ -460,6 +497,7 @@ void CModem::clock(unsigned int ms) m_dmrSpace2 = m_buffer[8U]; m_ysfSpace = m_buffer[9U]; m_p25Space = m_buffer[10U]; + m_nxdnSpace = m_buffer[11U]; m_inactivityTimer.start(); // LogMessage("status=%02X, tx=%d, space=%u,%u,%u,%u,%u lockout=%d, cd=%d", m_buffer[5U], int(m_tx), m_dstarSpace, m_dmrSpace1, m_dmrSpace2, m_ysfSpace, m_p25Space, int(m_lockout), int(m_cd)); @@ -603,6 +641,23 @@ void CModem::clock(unsigned int ms) m_p25Space--; } + + if (m_nxdnSpace > 1U && !m_txNXDNData.isEmpty()) { + unsigned char len = 0U; + m_txNXDNData.getData(&len, 1U); + m_txNXDNData.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX NXDN Data", m_buffer, len); + + int ret = m_serial.write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing NXDN data to the MMDVM"); + + m_playoutTimer.start(); + + m_nxdnSpace--; + } } void CModem::close() @@ -682,6 +737,20 @@ unsigned int CModem::readP25Data(unsigned char* data) return len; } +unsigned int CModem::readNXDNData(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxNXDNData.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxNXDNData.getData(&len, 1U); + m_rxNXDNData.getData(data, len); + + return len; +} + // To be implemented later if needed unsigned int CModem::readSerial(unsigned char* data, unsigned int length) { @@ -852,6 +921,36 @@ bool CModem::writeP25Data(const unsigned char* data, unsigned int length) return true; } +bool CModem::hasNXDNSpace() const +{ + unsigned int space = m_txNXDNData.freeSpace() / (NXDN_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeNXDNData(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + if (data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + + unsigned char buffer[130U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = MMDVM_NXDN_DATA; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txNXDNData.addData(&len, 1U); + m_txNXDNData.addData(buffer, len); + + return true; +} + bool CModem::writeSerial(const unsigned char* data, unsigned int length) { assert(data != NULL); @@ -958,7 +1057,7 @@ bool CModem::setConfig() buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 18U; + buffer[1U] = 19U; buffer[2U] = MMDVM_SET_CONFIG; @@ -985,6 +1084,8 @@ bool CModem::setConfig() buffer[4U] |= 0x04U; if (m_p25Enabled) buffer[4U] |= 0x08U; + if (m_nxdnEnabled) + buffer[4U] |= 0x10U; buffer[5U] = m_txDelay / 10U; // In 10ms units @@ -1008,10 +1109,12 @@ bool CModem::setConfig() buffer[16U] = (unsigned char)(m_txDCOffset + 128); buffer[17U] = (unsigned char)(m_rxDCOffset + 128); - // CUtils::dump(1U, "Written", buffer, 18U); + buffer[18U] = (unsigned char)(m_nxdnTXLevel * 2.55F + 0.5F); - int ret = m_serial.write(buffer, 18U); - if (ret != 18) + // CUtils::dump(1U, "Written", buffer, 19U); + + int ret = m_serial.write(buffer, 19U); + if (ret != 19) return false; unsigned int count = 0U; diff --git a/Modem.h b/Modem.h index abed2b3..e335fbb 100644 --- a/Modem.h +++ b/Modem.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2011-2018 by Jonathan Naylor G4KLX * * 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 @@ -38,8 +38,8 @@ public: ~CModem(); void setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int txFrequency, int txOffset, int txDCOffset, int rxDCOffset, float rfLevel); - void setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled); - void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25Enabled); + void setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled); + void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel); void setDMRParams(unsigned int colorCode); void setYSFParams(bool loDev); @@ -50,6 +50,7 @@ public: unsigned int readDMRData2(unsigned char* data); unsigned int readYSFData(unsigned char* data); unsigned int readP25Data(unsigned char* data); + unsigned int readNXDNData(unsigned char* data); unsigned int readSerial(unsigned char* data, unsigned int length); @@ -58,6 +59,7 @@ public: bool hasDMRSpace2() const; bool hasYSFSpace() const; bool hasP25Space() const; + bool hasNXDNSpace() const; bool hasTX() const; bool hasCD() const; @@ -70,6 +72,7 @@ public: bool writeDMRData2(const unsigned char* data, unsigned int length); bool writeYSFData(const unsigned char* data, unsigned int length); bool writeP25Data(const unsigned char* data, unsigned int length); + bool writeNXDNData(const unsigned char* data, unsigned int length); bool writeDMRStart(bool tx); bool writeDMRShortLC(const unsigned char* lc); @@ -103,6 +106,7 @@ private: float m_dmrTXLevel; float m_ysfTXLevel; float m_p25TXLevel; + float m_nxdnTXLevel; float m_rfLevel; bool m_trace; bool m_debug; @@ -112,6 +116,7 @@ private: bool m_dmrEnabled; bool m_ysfEnabled; bool m_p25Enabled; + bool m_nxdnEnabled; int m_rxDCOffset; int m_txDCOffset; CSerialController m_serial; @@ -128,6 +133,8 @@ private: CRingBuffer m_txYSFData; CRingBuffer m_rxP25Data; CRingBuffer m_txP25Data; + CRingBuffer m_rxNXDNData; + CRingBuffer m_txNXDNData; CTimer m_statusTimer; CTimer m_inactivityTimer; CTimer m_playoutTimer; @@ -136,6 +143,7 @@ private: unsigned int m_dmrSpace2; unsigned int m_ysfSpace; unsigned int m_p25Space; + unsigned int m_nxdnSpace; bool m_tx; bool m_cd; bool m_lockout; diff --git a/NXDNControl.cpp b/NXDNControl.cpp new file mode 100644 index 0000000..c23c177 --- /dev/null +++ b/NXDNControl.cpp @@ -0,0 +1,1244 @@ +/* + * Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "NXDNControl.h" +#include "Utils.h" +#include "Sync.h" +#include "Log.h" + +#include +#include +#include +#include + +// #define DUMP_NXDN + +CNXDNControl::CNXDNControl(const std::string& callsign, bool selfOnly, CYSFNetwork* network, CDisplay* display, unsigned int timeout, bool duplex, bool lowDeviation, bool remoteGateway, CRSSIInterpolator* rssiMapper) : +m_callsign(NULL), +m_selfCallsign(NULL), +m_selfOnly(selfOnly), +m_network(network), +m_display(display), +m_duplex(duplex), +m_lowDeviation(lowDeviation), +m_remoteGateway(remoteGateway), +m_sqlEnabled(false), +m_sqlValue(0U), +m_queue(5000U, "NXDN Control"), +m_rfState(RS_RF_LISTENING), +m_netState(RS_NET_IDLE), +m_rfTimeoutTimer(1000U, timeout), +m_netTimeoutTimer(1000U, timeout), +m_packetTimer(1000U, 0U, 200U), +m_networkWatchdog(1000U, 0U, 1500U), +m_elapsed(), +m_rfFrames(0U), +m_netFrames(0U), +m_netLost(0U), +m_rfErrs(0U), +m_rfBits(1U), +m_netErrs(0U), +m_netBits(1U), +m_rfSource(NULL), +m_rfDest(NULL), +m_netSource(NULL), +m_netDest(NULL), +m_lastFICH(), +m_netN(0U), +m_rfPayload(), +m_netPayload(), +m_rssiMapper(rssiMapper), +m_rssi(0U), +m_maxRSSI(0U), +m_minRSSI(0U), +m_aveRSSI(0U), +m_rssiCount(0U), +m_fp(NULL) +{ + assert(display != NULL); + assert(rssiMapper != NULL); + + m_rfPayload.setUplink(callsign); + m_rfPayload.setDownlink(callsign); + + m_netPayload.setDownlink(callsign); + + m_netSource = new unsigned char[YSF_CALLSIGN_LENGTH]; + m_netDest = new unsigned char[YSF_CALLSIGN_LENGTH]; + + m_callsign = new unsigned char[YSF_CALLSIGN_LENGTH]; + + std::string node = callsign; + node.resize(YSF_CALLSIGN_LENGTH, ' '); + + for (unsigned int i = 0U; i < YSF_CALLSIGN_LENGTH; i++) + m_callsign[i] = node.at(i); + + m_selfCallsign = new unsigned char[YSF_CALLSIGN_LENGTH]; + ::memset(m_selfCallsign, 0x00U, YSF_CALLSIGN_LENGTH); + + for (unsigned int i = 0U; i < callsign.length(); i++) + m_selfCallsign[i] = callsign.at(i); +} + +CNXDNControl::~CNXDNControl() +{ + delete[] m_netSource; + delete[] m_netDest; + delete[] m_callsign; + delete[] m_selfCallsign; +} + +void CNXDNControl::setSQL(bool on, unsigned char value) +{ + m_sqlEnabled = on; + m_sqlValue = value; +} + +bool CNXDNControl::writeModem(unsigned char *data, unsigned int len) +{ + assert(data != NULL); + + unsigned char type = data[0U]; + + if (type == TAG_LOST && m_rfState == RS_RF_AUDIO) { + if (m_rssi != 0U) + LogMessage("NXDN, transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("NXDN, transmission lost, %.1f seconds, BER: %.1f%%", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits)); + writeEndRF(); + return false; + } + + if (type == TAG_LOST && m_rfState == RS_RF_REJECTED) { + m_rfPayload.reset(); + m_rfSource = NULL; + m_rfDest = NULL; + m_rfState = RS_RF_LISTENING; + return false; + } + + if (type == TAG_LOST) { + m_rfPayload.reset(); + m_rfState = RS_RF_LISTENING; + return false; + } + + // Have we got RSSI bytes on the end? + if (len == (NXDN_FRAME_LENGTH_BYTES + 4U)) { + uint16_t raw = 0U; + raw |= (data[122U] << 8) & 0xFF00U; + raw |= (data[123U] << 0) & 0x00FFU; + + // Convert the raw RSSI to dBm + int rssi = m_rssiMapper->interpolate(raw); + LogDebug("NXDN, raw RSSI: %u, reported RSSI: %d dBm", raw, rssi); + + // RSSI is always reported as positive + m_rssi = (rssi >= 0) ? rssi : -rssi; + + if (m_rssi > m_minRSSI) + m_minRSSI = m_rssi; + if (m_rssi < m_maxRSSI) + m_maxRSSI = m_rssi; + + m_aveRSSI += m_rssi; + m_rssiCount++; + } + + CYSFFICH fich; + bool valid = fich.decode(data + 2U); + + if (valid) + m_lastFICH = fich; + + // Validate the DSQ/DG-ID value if enabled + if (m_sqlEnabled) { + unsigned char cm = m_lastFICH.getCM(); + if (cm == YSF_CM_GROUP2) { + // Using the DG-ID value + unsigned char value = m_lastFICH.getSQ(); + + if (value != m_sqlValue) + return false; + } else { + // Using the DSQ value + bool sql = m_lastFICH.getSQL(); + unsigned char value = m_lastFICH.getSQ(); + + if (!sql || value != m_sqlValue) + return false; + } + } + + // Stop repeater packets coming through, unless we're acting as a remote gateway + if (m_remoteGateway) { + unsigned char mr = m_lastFICH.getMR(); + if (mr != YSF_MR_BUSY) + return false; + } else { + unsigned char mr = m_lastFICH.getMR(); + if (mr == YSF_MR_BUSY) + return false; + } + + unsigned char dt = m_lastFICH.getDT(); + + bool ret = false; + switch (dt) { + case YSF_DT_VOICE_FR_MODE: + ret = processVWData(valid, data); + break; + + case YSF_DT_VD_MODE1: + case YSF_DT_VD_MODE2: + ret = processDNData(valid, data); + break; + + case YSF_DT_DATA_FR_MODE: + ret = processFRData(valid, data); + break; + + default: + break; + } + + return ret; +} + +bool CNXDNControl::processVWData(bool valid, unsigned char *data) +{ + unsigned char fi = m_lastFICH.getFI(); + if (valid && fi == YSF_FI_HEADER) { + if (m_rfState == RS_RF_LISTENING) { + bool valid = m_rfPayload.processHeaderData(data + 2U); + if (!valid) + return false; + + m_rfSource = m_rfPayload.getSource(); + + if (m_selfOnly) { + bool ret = checkCallsign(m_rfSource); + if (!ret) { + LogMessage("YSF, invalid access attempt from %10.10s", m_rfSource); + m_rfState = RS_RF_REJECTED; + return false; + } + } + + unsigned char cm = m_lastFICH.getCM(); + if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2) + m_rfDest = (unsigned char*)"ALL "; + else + m_rfDest = m_rfPayload.getDest(); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfTimeoutTimer.start(); + m_rfState = RS_RF_AUDIO; + + m_minRSSI = m_rssi; + m_maxRSSI = m_rssi; + m_aveRSSI = m_rssi; + m_rssiCount = 1U; +#if defined(DUMP_YSF) + openFile(); +#endif + + m_display->writeFusion((char*)m_rfSource, (char*)m_rfDest, "R", " "); + LogMessage("YSF, received RF header from %10.10s to %10.10s", m_rfSource, m_rfDest); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } else if (valid && fi == YSF_FI_TERMINATOR) { + if (m_rfState == RS_RF_REJECTED) { + m_rfPayload.reset(); + m_rfSource = NULL; + m_rfDest = NULL; + m_rfState = RS_RF_LISTENING; + } else if (m_rfState == RS_RF_AUDIO) { + m_rfPayload.processHeaderData(data + 2U); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + if (m_rssi != 0U) + LogMessage("YSF, received RF end of transmission, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("YSF, received RF end of transmission, %.1f seconds, BER: %.1f%%", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits)); + + writeEndRF(); + } + } else { + if (m_rfState == RS_RF_AUDIO) { + // If valid is false, update the m_lastFICH for this transmission + if (!valid) { + // XXX Check these values + m_lastFICH.setFT(0U); + m_lastFICH.setFN(0U); + } + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + unsigned char fn = fich.getFN(); + unsigned char ft = fich.getFT(); + + if (fn != 0U || ft != 1U) { + // The first packet after the header is odd, don't try and regenerate it + unsigned int errors = m_rfPayload.processVoiceFRModeAudio(data + 2U); + m_rfErrs += errors; + m_rfBits += 720U; + m_display->writeFusionBER(float(errors) / 7.2F); + LogDebug("YSF, V Mode 3, seq %u, AMBE FEC %u/720 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 7.2F); + } + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } + + return false; +} + +bool CNXDNControl::processDNData(bool valid, unsigned char *data) +{ + unsigned char fi = m_lastFICH.getFI(); + if (valid && fi == YSF_FI_HEADER) { + if (m_rfState == RS_RF_LISTENING) { + bool valid = m_rfPayload.processHeaderData(data + 2U); + if (!valid) + return false; + + m_rfSource = m_rfPayload.getSource(); + + if (m_selfOnly) { + bool ret = checkCallsign(m_rfSource); + if (!ret) { + LogMessage("YSF, invalid access attempt from %10.10s", m_rfSource); + m_rfState = RS_RF_REJECTED; + return false; + } + } + + unsigned char cm = m_lastFICH.getCM(); + if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2) + m_rfDest = (unsigned char*)"ALL "; + else + m_rfDest = m_rfPayload.getDest(); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfTimeoutTimer.start(); + m_rfState = RS_RF_AUDIO; + + m_minRSSI = m_rssi; + m_maxRSSI = m_rssi; + m_aveRSSI = m_rssi; + m_rssiCount = 1U; +#if defined(DUMP_YSF) + openFile(); +#endif + + m_display->writeFusion((char*)m_rfSource, (char*)m_rfDest, "R", " "); + LogMessage("YSF, received RF header from %10.10s to %10.10s", m_rfSource, m_rfDest); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } else if (valid && fi == YSF_FI_TERMINATOR) { + if (m_rfState == RS_RF_REJECTED) { + m_rfPayload.reset(); + m_rfSource = NULL; + m_rfDest = NULL; + m_rfState = RS_RF_LISTENING; + } else if (m_rfState == RS_RF_AUDIO) { + m_rfPayload.processHeaderData(data + 2U); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + if (m_rssi != 0U) + LogMessage("YSF, received RF end of transmission, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("YSF, received RF end of transmission, %.1f seconds, BER: %.1f%%", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits)); + + writeEndRF(); + } + } else { + if (m_rfState == RS_RF_AUDIO) { + // If valid is false, update the m_lastFICH for this transmission + if (!valid) { + unsigned char ft = m_lastFICH.getFT(); + unsigned char fn = m_lastFICH.getFN() + 1U; + + if (fn > ft) + fn = 0U; + + m_lastFICH.setFN(fn); + } + + CSync::addYSFSync(data + 2U); + + unsigned char fn = m_lastFICH.getFN(); + unsigned char dt = m_lastFICH.getDT(); + + switch (dt) { + case YSF_DT_VD_MODE1: { + m_rfPayload.processVDMode1Data(data + 2U, fn); + unsigned int errors = m_rfPayload.processVDMode1Audio(data + 2U); + m_rfErrs += errors; + m_rfBits += 235U; + m_display->writeFusionBER(float(errors) / 2.35F); + LogDebug("YSF, V/D Mode 1, seq %u, AMBE FEC %u/235 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 2.35F); + } + break; + + case YSF_DT_VD_MODE2: { + m_rfPayload.processVDMode2Data(data + 2U, fn); + unsigned int errors = m_rfPayload.processVDMode2Audio(data + 2U); + m_rfErrs += errors; + m_rfBits += 135U; + m_display->writeFusionBER(float(errors) / 1.35F); + LogDebug("YSF, V/D Mode 2, seq %u, Repetition FEC %u/135 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 1.35F); + } + break; + + default: + break; + } + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } else if (valid && m_rfState == RS_RF_LISTENING) { + // Only use clean frames for late entry. + unsigned char fn = m_lastFICH.getFN(); + unsigned char dt = m_lastFICH.getDT(); + + switch (dt) { + case YSF_DT_VD_MODE1: + valid = m_rfPayload.processVDMode1Data(data + 2U, fn); + break; + + case YSF_DT_VD_MODE2: + valid = m_rfPayload.processVDMode2Data(data + 2U, fn); + break; + + default: + valid = false; + break; + } + + if (!valid) + return false; + + unsigned char cm = m_lastFICH.getCM(); + if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2) + m_rfDest = (unsigned char*)"ALL "; + else + m_rfDest = m_rfPayload.getDest(); + + m_rfSource = m_rfPayload.getSource(); + + if (m_rfSource == NULL || m_rfDest == NULL) + return false; + + if (m_selfOnly) { + bool ret = checkCallsign(m_rfSource); + if (!ret) { + LogMessage("YSF, invalid access attempt from %10.10s", m_rfSource); + m_rfState = RS_RF_REJECTED; + return false; + } + } + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfTimeoutTimer.start(); + m_rfState = RS_RF_AUDIO; + + m_minRSSI = m_rssi; + m_maxRSSI = m_rssi; + m_aveRSSI = m_rssi; + m_rssiCount = 1U; +#if defined(DUMP_YSF) + openFile(); +#endif + + // Build a new header and transmit it + unsigned char buffer[YSF_FRAME_LENGTH_BYTES + 2U]; + + CSync::addYSFSync(buffer + 2U); + + CYSFFICH fich = m_lastFICH; + fich.setFI(YSF_FI_HEADER); + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(buffer + 2U); + + unsigned char csd1[20U], csd2[20U]; + memcpy(csd1 + YSF_CALLSIGN_LENGTH, m_rfSource, YSF_CALLSIGN_LENGTH); + memset(csd2, ' ', YSF_CALLSIGN_LENGTH + YSF_CALLSIGN_LENGTH); + + if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2) + memset(csd1 + 0U, '*', YSF_CALLSIGN_LENGTH); + else + memcpy(csd1 + 0U, m_rfDest, YSF_CALLSIGN_LENGTH); + + CYSFPayload payload; + payload.writeHeader(buffer + 2U, csd1, csd2); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + writeNetwork(buffer, m_rfFrames % 128U); + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(buffer + 2U); + writeQueueRF(buffer); + } + +#if defined(DUMP_YSF) + writeFile(buffer + 2U); +#endif + + m_display->writeFusion((char*)m_rfSource, (char*)m_rfDest, "R", " "); + LogMessage("YSF, received RF late entry from %10.10s to %10.10s", m_rfSource, m_rfDest); + + CSync::addYSFSync(data + 2U); + + fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } + + return false; +} + +bool CNXDNControl::processFRData(bool valid, unsigned char *data) +{ + unsigned char fi = m_lastFICH.getFI(); + if (valid && fi == YSF_FI_HEADER) { + if (m_rfState == RS_RF_LISTENING) { + valid = m_rfPayload.processHeaderData(data + 2U); + if (!valid) + return false; + + m_rfSource = m_rfPayload.getSource(); + + if (m_selfOnly) { + bool ret = checkCallsign(m_rfSource); + if (!ret) { + LogMessage("YSF, invalid access attempt from %10.10s", m_rfSource); + m_rfState = RS_RF_REJECTED; + return false; + } + } + + unsigned char cm = m_lastFICH.getCM(); + if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2) + m_rfDest = (unsigned char*)"ALL "; + else + m_rfDest = m_rfPayload.getDest(); + + m_rfFrames = 0U; + m_rfState = RS_RF_DATA; + + m_minRSSI = m_rssi; + m_maxRSSI = m_rssi; + m_aveRSSI = m_rssi; + m_rssiCount = 1U; +#if defined(DUMP_YSF) + openFile(); +#endif + + m_display->writeFusion((char*)m_rfSource, (char*)m_rfDest, "R", " "); + LogMessage("YSF, received RF header from %10.10s to %10.10s", m_rfSource, m_rfDest); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } else if (valid && fi == YSF_FI_TERMINATOR) { + if (m_rfState == RS_RF_REJECTED) { + m_rfPayload.reset(); + m_rfSource = NULL; + m_rfDest = NULL; + m_rfState = RS_RF_LISTENING; + } else if (m_rfState == RS_RF_DATA) { + m_rfPayload.processHeaderData(data + 2U); + + CSync::addYSFSync(data + 2U); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + + m_rfFrames++; + + if (m_rssi != 0U) + LogMessage("YSF, received RF end of transmission, %.1f seconds, RSSI: -%u/-%u/-%u dBm", float(m_rfFrames) / 10.0F, m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("YSF, received RF end of transmission, %.1f seconds", float(m_rfFrames) / 10.0F); + + writeEndRF(); + } + } else { + if (m_rfState == RS_RF_DATA) { + // If valid is false, update the m_lastFICH for this transmission + if (!valid) { + unsigned char ft = m_lastFICH.getFT(); + unsigned char fn = m_lastFICH.getFN() + 1U; + + if (fn > ft) + fn = 0U; + + m_lastFICH.setFN(fn); + } + + CSync::addYSFSync(data + 2U); + + unsigned char fn = m_lastFICH.getFN(); + + m_rfPayload.processDataFRModeData(data + 2U, fn); + + CYSFFICH fich = m_lastFICH; + + // Remove any DSQ information + fich.setSQL(false); + fich.setSQ(0U); + fich.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + writeNetwork(data, m_rfFrames % 128U); + + if (m_duplex) { + // Add the DSQ information. + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 2U); + writeQueueRF(data); + } + +#if defined(DUMP_YSF) + writeFile(data + 2U); +#endif + + m_rfFrames++; + + m_display->writeFusionRSSI(m_rssi); + + return true; + } + } + + return false; +} + +unsigned int CNXDNControl::readModem(unsigned char* data) +{ + assert(data != NULL); + + if (m_queue.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_queue.getData(&len, 1U); + + m_queue.getData(data, len); + + return len; +} + +void CNXDNControl::writeEndRF() +{ + m_rfState = RS_RF_LISTENING; + + m_rfTimeoutTimer.stop(); + m_rfPayload.reset(); + + // These variables are free'd by YSFPayload + m_rfSource = NULL; + m_rfDest = NULL; + + if (m_netState == RS_NET_IDLE) { + m_display->clearFusion(); + + if (m_network != NULL) + m_network->reset(); + } + +#if defined(DUMP_YSF) + closeFile(); +#endif +} + +void CNXDNControl::writeEndNet() +{ + m_netState = RS_NET_IDLE; + + m_netTimeoutTimer.stop(); + m_networkWatchdog.stop(); + m_packetTimer.stop(); + + m_netPayload.reset(); + + m_display->clearFusion(); + + if (m_network != NULL) + m_network->reset(); +} + +void CNXDNControl::writeNetwork() +{ + unsigned char data[200U]; + unsigned int length = m_network->read(data); + if (length == 0U) + return; + + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) + return; + + m_networkWatchdog.start(); + + bool gateway = ::memcmp(data + 4U, m_callsign, YSF_CALLSIGN_LENGTH) == 0; + + unsigned char n = (data[34U] & 0xFEU) >> 1; + bool end = (data[34U] & 0x01U) == 0x01U; + + if (!m_netTimeoutTimer.isRunning()) { + if (end) + return; + + if (::memcmp(data + 14U, " ", YSF_CALLSIGN_LENGTH) != 0) + ::memcpy(m_netSource, data + 14U, YSF_CALLSIGN_LENGTH); + else + ::memcpy(m_netSource, "??????????", YSF_CALLSIGN_LENGTH); + + if (::memcmp(data + 24U, " ", YSF_CALLSIGN_LENGTH) != 0) + ::memcpy(m_netDest, data + 24U, YSF_CALLSIGN_LENGTH); + else + ::memcpy(m_netDest, "??????????", YSF_CALLSIGN_LENGTH); + + m_display->writeFusion((char*)m_netSource, (char*)m_netDest, "N", (char*)(data + 4U)); + LogMessage("YSF, received network data from %10.10s to %10.10s at %10.10s", m_netSource, m_netDest, data + 4U); + + m_netTimeoutTimer.start(); + m_netPayload.reset(); + m_packetTimer.start(); + m_elapsed.start(); + m_netState = RS_NET_AUDIO; + m_netFrames = 0U; + m_netLost = 0U; + m_netErrs = 0U; + m_netBits = 1U; + m_netN = 0U; + } else { + // Check for duplicate frames, if we can + if (m_netN == n) + return; + + bool changed = false; + + if (::memcmp(data + 14U, " ", YSF_CALLSIGN_LENGTH) != 0 && ::memcmp(m_netSource, "??????????", YSF_CALLSIGN_LENGTH) == 0) { + ::memcpy(m_netSource, data + 14U, YSF_CALLSIGN_LENGTH); + changed = true; + } + + if (::memcmp(data + 24U, " ", YSF_CALLSIGN_LENGTH) != 0 && ::memcmp(m_netDest, "??????????", YSF_CALLSIGN_LENGTH) == 0) { + ::memcpy(m_netDest, data + 24U, YSF_CALLSIGN_LENGTH); + changed = true; + } + + if (changed) { + m_display->writeFusion((char*)m_netSource, (char*)m_netDest, "N", (char*)(data + 4U)); + LogMessage("YSF, received network data from %10.10s to %10.10s at %10.10s", m_netSource, m_netDest, data + 4U); + } + } + + data[33U] = end ? TAG_EOT : TAG_DATA; + data[34U] = 0x00U; + + CYSFFICH fich; + bool valid = fich.decode(data + 35U); + if (valid) { + unsigned char dt = fich.getDT(); + unsigned char fn = fich.getFN(); + unsigned char ft = fich.getFT(); + unsigned char fi = fich.getFI(); + + // Add any DSQ information + fich.setSQL(m_sqlEnabled); + fich.setSQ(m_sqlValue); + + fich.setVoIP(true); + fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY); + fich.setDev(m_lowDeviation); + fich.encode(data + 35U); + + // Set the downlink callsign + switch (fi) { + case YSF_FI_HEADER: + case YSF_FI_TERMINATOR: + m_netPayload.processHeaderData(data + 35U); + break; + + case YSF_FI_COMMUNICATIONS: + switch (dt) { + case YSF_DT_VD_MODE1: { + m_netPayload.processVDMode1Data(data + 35U, fn, gateway); + unsigned int errors = m_netPayload.processVDMode1Audio(data + 35U); + m_netErrs += errors; + m_netBits += 235U; + } + break; + + case YSF_DT_VD_MODE2: { + m_netPayload.processVDMode2Data(data + 35U, fn, gateway); + unsigned int errors = m_netPayload.processVDMode2Audio(data + 35U); + m_netErrs += errors; + m_netBits += 135U; + } + break; + + case YSF_DT_DATA_FR_MODE: + m_netPayload.processDataFRModeData(data + 35U, fn, gateway); + break; + + case YSF_DT_VOICE_FR_MODE: + if (fn != 0U || ft != 1U) { + // The first packet after the header is odd, don't try and regenerate it + unsigned int errors = m_netPayload.processVoiceFRModeAudio(data + 35U); + m_netErrs += errors; + m_netBits += 720U; + } + break; + + default: + break; + } + break; + + default: + break; + } + } + + writeQueueNet(data + 33U); + + m_packetTimer.start(); + m_netFrames++; + m_netN = n; + + if (end) { + LogMessage("NXDN, received network end of transmission, %.1f seconds, %u%% packet loss, BER: %.1f%%", float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits)); + writeEndNet(); + } +} + +void CNXDNControl::clock(unsigned int ms) +{ + if (m_network != NULL) + writeNetwork(); + + m_rfTimeoutTimer.clock(ms); + m_netTimeoutTimer.clock(ms); + + if (m_netState == RS_NET_AUDIO) { + m_networkWatchdog.clock(ms); + + if (m_networkWatchdog.hasExpired()) { + LogMessage("NXDN, network watchdog has expired, %.1f seconds, %u%% packet loss, BER: %.1f%%", float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits)); + writeEndNet(); + } + } +} + +void CNXDNControl::writeQueueRF(const unsigned char *data) +{ + assert(data != NULL); + + if (m_netState != RS_NET_IDLE) + return; + + if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) + return; + + unsigned char len = NXDN_FRAME_LENGTH_BYTES + 2U; + + unsigned int space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError("NXDN, overflow in the NXDN RF queue"); + return; + } + + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +void CNXDNControl::writeQueueNet(const unsigned char *data) +{ + assert(data != NULL); + + if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) + return; + + unsigned char len = NXDN_FRAME_LENGTH_BYTES + 2U; + + unsigned int space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError("NXDN, overflow in the NXDN RF queue"); + return; + } + + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +void CNXDNControl::writeNetwork(const unsigned char *data, unsigned int count) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) + return; + + m_network->write(m_rfSource, m_rfDest, data + 2U, count, data[0U] == TAG_EOT); +} + +bool CNXDNControl::openFile() +{ + if (m_fp != NULL) + return true; + + time_t t; + ::time(&t); + + struct tm* tm = ::localtime(&t); + + char name[100U]; + ::sprintf(name, "NXDN_%04d%02d%02d_%02d%02d%02d.ambe", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + m_fp = ::fopen(name, "wb"); + if (m_fp == NULL) + return false; + + ::fwrite("NXDN", 1U, 3U, m_fp); + + return true; +} + +bool CNXDNControl::writeFile(const unsigned char* data) +{ + if (m_fp == NULL) + return false; + + ::fwrite(data, 1U, NXDN_FRAME_LENGTH_BYTES, m_fp); + + return true; +} + +void CNXDNControl::closeFile() +{ + if (m_fp != NULL) { + ::fclose(m_fp); + m_fp = NULL; + } +} + +bool CNXDNControl::checkCallsign(const unsigned char* callsign) const +{ + return ::memcmp(callsign, m_selfCallsign, ::strlen((char*)m_selfCallsign)) == 0; +} diff --git a/NXDNControl.h b/NXDNControl.h new file mode 100644 index 0000000..f2b7683 --- /dev/null +++ b/NXDNControl.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * + * 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(NXDNControl_H) +#define NXDNControl_H + +#include "RSSIInterpolator.h" +// #include "YSFNetwork.h" +#include "NXDNDefines.h" +// #include "YSFPayload.h" +#include "RingBuffer.h" +#include "StopWatch.h" +// #include "YSFFICH.h" +#include "Display.h" +#include "Defines.h" +#include "Timer.h" +#include "Modem.h" + +#include + +class CNXDNControl { +public: + CNXDNControl(const std::string& callsign, bool selfOnly, CYSFNetwork* network, CDisplay* display, unsigned int timeout, bool duplex, bool lowDeviation, bool remoteGateway, CRSSIInterpolator* rssiMapper); + ~CNXDNControl(); + + void setSQL(bool on, unsigned char value); + + bool writeModem(unsigned char* data, unsigned int len); + + unsigned int readModem(unsigned char* data); + + void clock(unsigned int ms); + +private: + unsigned char* m_callsign; + unsigned char* m_selfCallsign; + bool m_selfOnly; + CYSFNetwork* m_network; + CDisplay* m_display; + bool m_duplex; + bool m_lowDeviation; + bool m_remoteGateway; + bool m_sqlEnabled; + unsigned char m_sqlValue; + CRingBuffer m_queue; + RPT_RF_STATE m_rfState; + RPT_NET_STATE m_netState; + CTimer m_rfTimeoutTimer; + CTimer m_netTimeoutTimer; + CTimer m_packetTimer; + CTimer m_networkWatchdog; + CStopWatch m_elapsed; + unsigned int m_rfFrames; + unsigned int m_netFrames; + unsigned int m_netLost; + unsigned int m_rfErrs; + unsigned int m_rfBits; + unsigned int m_netErrs; + unsigned int m_netBits; + unsigned char* m_rfSource; + unsigned char* m_rfDest; + unsigned char* m_netSource; + unsigned char* m_netDest; + CYSFFICH m_lastFICH; + unsigned char m_netN; + CYSFPayload m_rfPayload; + CYSFPayload m_netPayload; + CRSSIInterpolator* m_rssiMapper; + unsigned char m_rssi; + unsigned char m_maxRSSI; + unsigned char m_minRSSI; + unsigned int m_aveRSSI; + unsigned int m_rssiCount; + FILE* m_fp; + + bool processVWData(bool valid, unsigned char *data); + bool processDNData(bool valid, unsigned char *data); + bool processFRData(bool valid, unsigned char *data); + + void writeQueueRF(const unsigned char* data); + void writeQueueNet(const unsigned char* data); + void writeNetwork(const unsigned char* data, unsigned int count); + void writeNetwork(); + + void writeEndRF(); + void writeEndNet(); + + bool openFile(); + bool writeFile(const unsigned char* data); + void closeFile(); + + bool checkCallsign(const unsigned char* callsign) const; +}; + +#endif diff --git a/NXDNDefines.h b/NXDNDefines.h new file mode 100644 index 0000000..21936bf --- /dev/null +++ b/NXDNDefines.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX + * + * 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(NXDNDEFINES_H) +#define NXDNDEFINES_H + +const unsigned int NXDN_RADIO_SYMBOL_LENGTH = 5U; // At 24 kHz sample rate + +const unsigned int NXDN_FRAME_LENGTH_BYTES = 48U; + +const unsigned int NXDN_FSW_LENGTH_BITS = 20U; +const unsigned int NXDN_FSW_LENGTH_SYMBOLS = NXDN_FSW_LENGTH_BITS / 2U; +const unsigned int NXDN_FSW_LENGTH_SAMPLES = NXDN_FSW_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + +const unsigned char NXDN_FSW_BYTES[] = {0x0CU, 0xDFU, 0x59U}; +const unsigned char NXDN_FSW_BYTES_MASK[] = {0x0FU, 0xFFU, 0xFFU}; +const unsigned int NXDN_FSW_BYTES_LENGTH = 3U; + +#endif +