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
+