diff --git a/Conf.cpp b/Conf.cpp index f594a18..9038b28 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -43,6 +43,7 @@ enum SECTION { SECTION_FUSION, SECTION_P25, SECTION_NXDN, + SECTION_M17, SECTION_POCSAG, SECTION_FM, SECTION_DSTAR_NETWORK, @@ -50,6 +51,7 @@ enum SECTION { SECTION_FUSION_NETWORK, SECTION_P25_NETWORK, SECTION_NXDN_NETWORK, + SECTION_M17_NETWORK, SECTION_POCSAG_NETWORK, SECTION_TFTSERIAL, SECTION_HD44780, @@ -102,6 +104,7 @@ m_modemDMRTXLevel(50.0F), m_modemYSFTXLevel(50.0F), m_modemP25TXLevel(50.0F), m_modemNXDNTXLevel(50.0F), +m_modemM17TXLevel(50.0F), m_modemPOCSAGTXLevel(50.0F), m_modemFMTXLevel(50.0F), m_modemRSSIMappingFile(), @@ -164,6 +167,10 @@ m_nxdnSelfOnly(false), m_nxdnRemoteGateway(false), m_nxdnTXHang(5U), m_nxdnModeHang(10U), +m_m17Enabled(false), +m_m17SelfOnly(false), +m_m17TXHang(5U), +m_m17ModeHang(10U), m_pocsagEnabled(false), m_pocsagFrequency(0U), m_fmEnabled(false), @@ -235,6 +242,12 @@ m_nxdnLocalAddress(), m_nxdnLocalPort(0U), m_nxdnNetworkModeHang(3U), m_nxdnNetworkDebug(false), +m_m17NetworkEnabled(false), +m_m17GatewayAddress(), +m_m17GatewayPort(0U), +m_m17LocalPort(0U), +m_m17NetworkModeHang(3U), +m_m17NetworkDebug(false), m_pocsagNetworkEnabled(false), m_pocsagGatewayAddress(), m_pocsagGatewayPort(0U), @@ -329,6 +342,8 @@ bool CConf::read() section = SECTION_P25; else if (::strncmp(buffer, "[NXDN]", 6U) == 0) section = SECTION_NXDN; + else if (::strncmp(buffer, "[M17]", 5U) == 0) + section = SECTION_M17; else if (::strncmp(buffer, "[POCSAG]", 8U) == 0) section = SECTION_POCSAG; else if (::strncmp(buffer, "[FM]", 4U) == 0) @@ -343,6 +358,8 @@ bool CConf::read() section = SECTION_P25_NETWORK; else if (::strncmp(buffer, "[NXDN Network]", 14U) == 0) section = SECTION_NXDN_NETWORK; + else if (::strncmp(buffer, "[M17 Network]", 13U) == 0) + section = SECTION_M17_NETWORK; else if (::strncmp(buffer, "[POCSAG Network]", 16U) == 0) section = SECTION_POCSAG_NETWORK; else if (::strncmp(buffer, "[TFT Serial]", 12U) == 0) @@ -404,12 +421,12 @@ bool CConf::read() else if (::strcmp(key, "Duplex") == 0) m_duplex = ::atoi(value) == 1; else if (::strcmp(key, "ModeHang") == 0) - m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = - m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = (unsigned int)::atoi(value); + m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = m_nxdnNetworkModeHang = m_m17NetworkModeHang = + m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = m_nxdnModeHang = m_m17ModeHang = (unsigned int)::atoi(value); else if (::strcmp(key, "RFModeHang") == 0) - m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = (unsigned int)::atoi(value); + m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = m_nxdnModeHang = m_m17ModeHang = (unsigned int)::atoi(value); else if (::strcmp(key, "NetModeHang") == 0) - m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = (unsigned int)::atoi(value); + m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = m_nxdnNetworkModeHang = m_m17NetworkModeHang = (unsigned int)::atoi(value); else if (::strcmp(key, "Display") == 0) m_display = value; else if (::strcmp(key, "Daemon") == 0) @@ -481,7 +498,7 @@ bool CConf::read() else if (::strcmp(key, "RXLevel") == 0) m_modemRXLevel = float(::atof(value)); else if (::strcmp(key, "TXLevel") == 0) - m_modemFMTXLevel = m_modemCWIdTXLevel = m_modemDStarTXLevel = m_modemDMRTXLevel = m_modemYSFTXLevel = m_modemP25TXLevel = m_modemNXDNTXLevel = float(::atof(value)); + m_modemFMTXLevel = m_modemCWIdTXLevel = m_modemDStarTXLevel = m_modemDMRTXLevel = m_modemYSFTXLevel = m_modemP25TXLevel = m_modemNXDNTXLevel = m_modemM17TXLevel = float(::atof(value)); else if (::strcmp(key, "CWIdTXLevel") == 0) m_modemCWIdTXLevel = float(::atof(value)); else if (::strcmp(key, "D-StarTXLevel") == 0) @@ -494,6 +511,8 @@ bool CConf::read() m_modemP25TXLevel = float(::atof(value)); else if (::strcmp(key, "NXDNTXLevel") == 0) m_modemNXDNTXLevel = float(::atof(value)); + else if (::strcmp(key, "M17TXLevel") == 0) + m_modemM17TXLevel = float(::atof(value)); else if (::strcmp(key, "POCSAGTXLevel") == 0) m_modemPOCSAGTXLevel = float(::atof(value)); else if (::strcmp(key, "FMTXLevel") == 0) @@ -682,13 +701,21 @@ bool CConf::read() m_nxdnTXHang = (unsigned int)::atoi(value); else if (::strcmp(key, "ModeHang") == 0) m_nxdnModeHang = (unsigned int)::atoi(value); + } else if (section == SECTION_M17) { + if (::strcmp(key, "Enable") == 0) + m_m17Enabled = ::atoi(value) == 1; + else if (::strcmp(key, "SelfOnly") == 0) + m_m17SelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "TXHang") == 0) + m_m17TXHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_m17ModeHang = (unsigned int)::atoi(value); } else if (section == SECTION_POCSAG) { - if (::strcmp(key, "Enable") == 0) - m_pocsagEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Frequency") == 0) - m_pocsagFrequency = (unsigned int)::atoi(value); - } - else if (section == SECTION_FM) { + if (::strcmp(key, "Enable") == 0) + m_pocsagEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Frequency") == 0) + m_pocsagFrequency = (unsigned int)::atoi(value); + } else if (section == SECTION_FM) { if (::strcmp(key, "Enable") == 0) m_fmEnabled = ::atoi(value) == 1; else if (::strcmp(key, "Callsign") == 0) { @@ -843,6 +870,19 @@ bool CConf::read() m_nxdnNetworkModeHang = (unsigned int)::atoi(value); else if (::strcmp(key, "Debug") == 0) m_nxdnNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_M17_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_m17NetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "LocalPort") == 0) + m_m17LocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "GatewayAddress") == 0) + m_m17GatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_m17GatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_m17NetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_m17NetworkDebug = ::atoi(value) == 1; } else if (section == SECTION_POCSAG_NETWORK) { if (::strcmp(key, "Enable") == 0) m_pocsagNetworkEnabled = ::atoi(value) == 1; @@ -1151,6 +1191,11 @@ float CConf::getModemNXDNTXLevel() const return m_modemNXDNTXLevel; } +float CConf::getModemM17TXLevel() const +{ + return m_modemM17TXLevel; +} + float CConf::getModemPOCSAGTXLevel() const { return m_modemPOCSAGTXLevel; @@ -1461,6 +1506,26 @@ unsigned int CConf::getNXDNModeHang() const return m_nxdnModeHang; } +bool CConf::getM17Enabled() const +{ + return m_m17Enabled; +} + +bool CConf::getM17SelfOnly() const +{ + return m_m17SelfOnly; +} + +unsigned int CConf::getM17TXHang() const +{ + return m_m17TXHang; +} + +unsigned int CConf::getM17ModeHang() const +{ + return m_m17ModeHang; +} + bool CConf::getPOCSAGEnabled() const { return m_pocsagEnabled; @@ -1816,6 +1881,36 @@ bool CConf::getNXDNNetworkDebug() const return m_nxdnNetworkDebug; } +bool CConf::getM17NetworkEnabled() const +{ + return m_m17NetworkEnabled; +} + +std::string CConf::getM17GatewayAddress() const +{ + return m_m17GatewayAddress; +} + +unsigned int CConf::getM17GatewayPort() const +{ + return m_m17GatewayPort; +} + +unsigned int CConf::getM17LocalPort() const +{ + return m_m17LocalPort; +} + +unsigned int CConf::getM17NetworkModeHang() const +{ + return m_m17NetworkModeHang; +} + +bool CConf::getM17NetworkDebug() const +{ + return m_m17NetworkDebug; +} + bool CConf::getPOCSAGNetworkEnabled() const { return m_pocsagNetworkEnabled; diff --git a/Conf.h b/Conf.h index 2b04fa5..9470746 100644 --- a/Conf.h +++ b/Conf.h @@ -83,6 +83,7 @@ public: float getModemYSFTXLevel() const; float getModemP25TXLevel() const; float getModemNXDNTXLevel() const; + float getModemM17TXLevel() const; float getModemPOCSAGTXLevel() const; float getModemFMTXLevel() const; std::string getModemRSSIMappingFile() const; @@ -160,6 +161,12 @@ public: unsigned int getNXDNTXHang() const; unsigned int getNXDNModeHang() const; + // The M17 section + bool getM17Enabled() const; + bool getM17SelfOnly() const; + unsigned int getM17TXHang() const; + unsigned int getM17ModeHang() const; + // The POCSAG section bool getPOCSAGEnabled() const; unsigned int getPOCSAGFrequency() const; @@ -245,6 +252,14 @@ public: unsigned int getNXDNNetworkModeHang() const; bool getNXDNNetworkDebug() const; + // The M17 Network section + bool getM17NetworkEnabled() const; + std::string getM17GatewayAddress() const; + unsigned int getM17GatewayPort() const; + unsigned int getM17LocalPort() const; + unsigned int getM17NetworkModeHang() const; + bool getM17NetworkDebug() const; + // The POCSAG Network section bool getPOCSAGNetworkEnabled() const; std::string getPOCSAGGatewayAddress() const; @@ -352,6 +367,7 @@ private: float m_modemYSFTXLevel; float m_modemP25TXLevel; float m_modemNXDNTXLevel; + float m_modemM17TXLevel; float m_modemPOCSAGTXLevel; float m_modemFMTXLevel; std::string m_modemRSSIMappingFile; @@ -422,6 +438,11 @@ private: unsigned int m_nxdnTXHang; unsigned int m_nxdnModeHang; + bool m_m17Enabled; + bool m_m17SelfOnly; + unsigned int m_m17TXHang; + unsigned int m_m17ModeHang; + bool m_pocsagEnabled; unsigned int m_pocsagFrequency; @@ -500,6 +521,13 @@ private: unsigned int m_nxdnNetworkModeHang; bool m_nxdnNetworkDebug; + bool m_m17NetworkEnabled; + std::string m_m17GatewayAddress; + unsigned int m_m17GatewayPort; + unsigned int m_m17LocalPort; + unsigned int m_m17NetworkModeHang; + bool m_m17NetworkDebug; + bool m_pocsagNetworkEnabled; std::string m_pocsagGatewayAddress; unsigned int m_pocsagGatewayPort; diff --git a/Defines.h b/Defines.h index 18c103e..81973ff 100644 --- a/Defines.h +++ b/Defines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020 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 @@ -26,6 +26,7 @@ const unsigned char MODE_YSF = 3U; const unsigned char MODE_P25 = 4U; const unsigned char MODE_NXDN = 5U; const unsigned char MODE_POCSAG = 6U; +const unsigned char MODE_M17 = 7U; const unsigned char MODE_FM = 10U; diff --git a/M17Control.cpp b/M17Control.cpp new file mode 100644 index 0000000..a2495f6 --- /dev/null +++ b/M17Control.cpp @@ -0,0 +1,1130 @@ +/* + * Copyright (C) 2015-2020 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 "M17Control.h" +#include "Utils.h" +#include "Sync.h" +#include "Log.h" + +#include +#include +#include +#include + +const unsigned char SCRAMBLER[] = { + 0x00U, 0x00U, 0x00U, 0x82U, 0xA0U, 0x88U, 0x8AU, 0x00U, 0xA2U, 0xA8U, 0x82U, 0x8AU, 0x82U, 0x02U, + 0x20U, 0x08U, 0x8AU, 0x20U, 0xAAU, 0xA2U, 0x82U, 0x08U, 0x22U, 0x8AU, 0xAAU, 0x08U, 0x28U, 0x88U, + 0x28U, 0x28U, 0x00U, 0x0AU, 0x02U, 0x82U, 0x20U, 0x28U, 0x82U, 0x2AU, 0xAAU, 0x20U, 0x22U, 0x80U, + 0xA8U, 0x8AU, 0x08U, 0xA0U, 0xAAU, 0x02U }; + +// #define DUMP_M17 + +const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +CM17Control::CM17Control(const std::string& callsign, bool selfOnly, CM17Network* network, CDisplay* display, unsigned int timeout, bool duplex, CRSSIInterpolator* rssiMapper) : +m_callsign(callsign), +m_selfOnly(selfOnly), +m_network(network), +m_display(display), +m_duplex(duplex), +m_queue(5000U, "M17 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_rfErrs(0U), +m_rfBits(1U), +m_rfLastLICH(), +m_rfLayer3(), +m_netLayer3(), +m_rfMask(0x00U), +m_netMask(0x00U), +m_rssiMapper(rssiMapper), +m_rssi(0U), +m_maxRSSI(0U), +m_minRSSI(0U), +m_aveRSSI(0U), +m_rssiCount(0U), +m_enabled(true), +m_fp(NULL) +{ + assert(display != NULL); + assert(rssiMapper != NULL); +} + +CM17Control::~CM17Control() +{ +} + +bool CM17Control::writeModem(unsigned char *data, unsigned int len) +{ + assert(data != NULL); + + if (!m_enabled) + return false; + + unsigned char type = data[0U]; + + if (type == TAG_LOST && m_rfState == RS_RF_AUDIO) { + unsigned short dstId = m_rfLayer3.getDestinationGroupId(); + bool grp = m_rfLayer3.getIsGroup(); + std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); + + if (m_rssi != 0U) + LogMessage("M17, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("M17, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); + writeEndRF(); + return false; + } + + if (type == TAG_LOST && m_rfState == RS_RF_DATA) { + writeEndRF(); + return false; + } + + if (type == TAG_LOST) { + m_rfState = RS_RF_LISTENING; + m_rfMask = 0x00U; + m_rfLayer3.reset(); + return false; + } + + // Have we got RSSI bytes on the end? + if (len == (M17_FRAME_LENGTH_BYTES + 4U)) { + uint16_t raw = 0U; + raw |= (data[50U] << 8) & 0xFF00U; + raw |= (data[51U] << 0) & 0x00FFU; + + // Convert the raw RSSI to dBm + int rssi = m_rssiMapper->interpolate(raw); + if (rssi != 0) + LogDebug("M17, 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++; + } + + scrambler(data + 2U); + + CM17LICH lich; + bool valid = lich.decode(data + 2U); + + if (valid) + m_rfLastLICH = lich; + + // Stop repeater packets coming through, unless we're acting as a remote gateway + if (m_remoteGateway) { + unsigned char direction = m_rfLastLICH.getDirection(); + if (direction == M17_LICH_DIRECTION_INBOUND) + return false; + } else { + unsigned char direction = m_rfLastLICH.getDirection(); + if (direction == M17_LICH_DIRECTION_OUTBOUND) + return false; + } + + unsigned char usc = m_rfLastLICH.getFCT(); + unsigned char option = m_rfLastLICH.getOption(); + + bool ret; + if (usc == M17_LICH_USC_UDCH) + ret = processData(option, data); + else + ret = processVoice(usc, option, data); + + return ret; +} + +bool CM17Control::processVoice(unsigned char usc, unsigned char option, unsigned char *data) +{ + CM17SACCH sacch; + bool valid = sacch.decode(data + 2U); + if (valid) { + unsigned char ran = sacch.getRAN(); + if (ran != m_ran && ran != 0U) + return false; + } else if (m_rfState == RS_RF_LISTENING) { + return false; + } + + unsigned char netData[40U]; + ::memset(netData, 0x00U, 40U); + + if (usc == M17_LICH_USC_SACCH_NS) { + // The SACCH on a non-superblock frame is usually an idle and not interesting apart from the RAN. + CM17FACCH1 facch; + bool valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + if (!valid) + valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + if (!valid) + return false; + + unsigned char buffer[10U]; + facch.getData(buffer); + + CM17Layer3 layer3; + layer3.decode(buffer, M17_FACCH1_LENGTH_BITS); + + unsigned char type = layer3.getMessageType(); + if (type == M17_MESSAGE_TYPE_TX_REL) { + if (m_rfState != RS_RF_AUDIO) { + m_rfState = RS_RF_LISTENING; + m_rfMask = 0x00U; + m_rfLayer3.reset(); + return false; + } + } else if (type == M17_MESSAGE_TYPE_VCALL) { + if (m_rfState == RS_RF_LISTENING && m_selfOnly) { + unsigned short srcId = layer3.getSourceUnitId(); + if (srcId != m_id) { + m_rfState = RS_RF_REJECTED; + return false; + } + } + } else { + return false; + } + + m_rfLayer3 = layer3; + + data[0U] = type == M17_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + CSync::addM17Sync(data + 2U); + + CM17LICH lich; + lich.setRFCT(M17_LICH_RFCT_RDCH); + lich.setFCT(M17_LICH_USC_SACCH_NS); + lich.setOption(M17_LICH_STEAL_FACCH); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + lich.setDirection(M17_LICH_DIRECTION_INBOUND); + netData[0U] = lich.getRaw(); + + CM17SACCH sacch; + sacch.setRAN(m_ran); + sacch.setStructure(M17_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(data + 2U); + + sacch.getRaw(netData + 1U); + + facch.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + + facch.getRaw(netData + 5U + 0U); + facch.getRaw(netData + 5U + 14U); + + scrambler(data + 2U); + + writeNetwork(netData, data[0U] == TAG_EOT ? NNMT_VOICE_TRAILER : NNMT_VOICE_HEADER); + +#if defined(DUMP_M17) + writeFile(data + 2U); +#endif + if (m_duplex) + writeQueueRF(data); + + if (data[0U] == TAG_EOT) { + unsigned short dstId = m_rfLayer3.getDestinationGroupId(); + bool grp = m_rfLayer3.getIsGroup(); + std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); + + m_rfFrames++; + if (m_rssi != 0U) + LogMessage("M17, received RF end of transmission from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + else + LogMessage("M17, received RF end of transmission from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); + writeEndRF(); + } else { + 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_M17) + openFile(); +#endif + unsigned short srcId = m_rfLayer3.getSourceUnitId(); + unsigned short dstId = m_rfLayer3.getDestinationGroupId(); + bool grp = m_rfLayer3.getIsGroup(); + + std::string source = m_lookup->find(srcId); + LogMessage("M17, received RF header from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); + m_display->writeM17(source.c_str(), grp, dstId, "R"); + } + + return true; + } else { + if (m_rfState == RS_RF_LISTENING) { + CM17FACCH1 facch; + bool valid = false; + switch (option) { + case M17_LICH_STEAL_FACCH: + valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + if (!valid) + valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + break; + case M17_LICH_STEAL_FACCH1_1: + valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + break; + case M17_LICH_STEAL_FACCH1_2: + valid = facch.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + break; + default: + break; + } + + bool hasInfo = false; + if (valid) { + unsigned char buffer[10U]; + facch.getData(buffer); + + CM17Layer3 layer3; + layer3.decode(buffer, M17_FACCH1_LENGTH_BITS); + + hasInfo = layer3.getMessageType() == M17_MESSAGE_TYPE_VCALL; + if (!hasInfo) + return false; + + m_rfLayer3 = layer3; + } + + if (!hasInfo) { + unsigned char message[3U]; + sacch.getData(message); + + unsigned char structure = sacch.getStructure(); + switch (structure) { + case M17_SR_1_4: + m_rfLayer3.decode(message, 18U, 0U); + if(m_rfLayer3.getMessageType() == M17_MESSAGE_TYPE_VCALL) + m_rfMask = 0x01U; + else + m_rfMask = 0x00U; + break; + case M17_SR_2_4: + m_rfMask |= 0x02U; + m_rfLayer3.decode(message, 18U, 18U); + break; + case M17_SR_3_4: + m_rfMask |= 0x04U; + m_rfLayer3.decode(message, 18U, 36U); + break; + case M17_SR_4_4: + m_rfMask |= 0x08U; + m_rfLayer3.decode(message, 18U, 54U); + break; + default: + break; + } + + if (m_rfMask != 0x0FU) + return false; + + unsigned char type = m_rfLayer3.getMessageType(); + if (type != M17_MESSAGE_TYPE_VCALL) + return false; + } + + unsigned short srcId = m_rfLayer3.getSourceUnitId(); + unsigned short dstId = m_rfLayer3.getDestinationGroupId(); + bool grp = m_rfLayer3.getIsGroup(); + + if (m_selfOnly) { + if (srcId != m_id) { + 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_M17) + openFile(); +#endif + std::string source = m_lookup->find(srcId); + LogMessage("M17, received RF late entry from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); + m_display->writeM17(source.c_str(), grp, dstId, "R"); + + m_rfState = RS_RF_AUDIO; + + // Create a dummy start message + unsigned char start[M17_FRAME_LENGTH_BYTES + 2U]; + + start[0U] = TAG_DATA; + start[1U] = 0x00U; + + // Generate the sync + CSync::addM17Sync(start + 2U); + + // Generate the LICH + CM17LICH lich; + lich.setRFCT(M17_LICH_RFCT_RDCH); + lich.setFCT(M17_LICH_USC_SACCH_NS); + lich.setOption(M17_LICH_STEAL_FACCH); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(start + 2U); + + lich.setDirection(M17_LICH_DIRECTION_INBOUND); + netData[0U] = lich.getRaw(); + + CM17SACCH sacch; + sacch.setRAN(m_ran); + sacch.setStructure(M17_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(start + 2U); + + sacch.getRaw(netData + 1U); + + unsigned char message[22U]; + m_rfLayer3.getData(message); + + facch.setData(message); + facch.encode(start + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch.encode(start + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + + facch.getRaw(netData + 5U + 0U); + facch.getRaw(netData + 5U + 14U); + + scrambler(start + 2U); + + writeNetwork(netData, NNMT_VOICE_HEADER); + +#if defined(DUMP_M17) + writeFile(start + 2U); +#endif + if (m_duplex) + writeQueueRF(start); + } + } + + if (m_rfState == RS_RF_AUDIO) { + // Regenerate the sync + CSync::addM17Sync(data + 2U); + + // Regenerate the LICH + CM17LICH lich; + lich.setRFCT(M17_LICH_RFCT_RDCH); + lich.setFCT(M17_LICH_USC_SACCH_SS); + lich.setOption(option); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + lich.setDirection(M17_LICH_DIRECTION_INBOUND); + netData[0U] = lich.getRaw(); + + // Regenerate SACCH if it's valid + CM17SACCH sacch; + bool validSACCH = sacch.decode(data + 2U); + if (validSACCH) { + sacch.setRAN(m_ran); + sacch.encode(data + 2U); + } + + sacch.getRaw(netData + 1U); + + // Regenerate the audio and interpret the FACCH1 data + if (option == M17_LICH_STEAL_NONE) { + CAMBEFEC ambe; + unsigned int errors = 0U; + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + m_rfErrs += errors; + m_rfBits += 188U; + m_display->writeM17BER(float(errors) / 1.88F); + LogDebug("M17, AMBE FEC %u/188 (%.1f%%)", errors, float(errors) / 1.88F); + + CM17Audio audio; + audio.decode(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 0U, netData + 5U + 0U); + audio.decode(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U, netData + 5U + 14U); + } else if (option == M17_LICH_STEAL_FACCH1_1) { + CM17FACCH1 facch1; + bool valid = facch1.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch1.getRaw(netData + 5U + 0U); + + CAMBEFEC ambe; + unsigned int errors = 0U; + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + m_rfErrs += errors; + m_rfBits += 94U; + m_display->writeM17BER(float(errors) / 0.94F); + LogDebug("M17, AMBE FEC %u/94 (%.1f%%)", errors, float(errors) / 0.94F); + + CM17Audio audio; + audio.decode(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U, netData + 5U + 14U); + } else if (option == M17_LICH_STEAL_FACCH1_2) { + CAMBEFEC ambe; + unsigned int errors = 0U; + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES); + errors += ambe.regenerateYSFDN(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + m_rfErrs += errors; + m_rfBits += 94U; + m_display->writeM17BER(float(errors) / 0.94F); + LogDebug("M17, AMBE FEC %u/94 (%.1f%%)", errors, float(errors) / 0.94F); + + CM17Audio audio; + audio.decode(data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 0U, netData + 5U + 0U); + + CM17FACCH1 facch1; + bool valid = facch1.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + if (valid) + facch1.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + facch1.getRaw(netData + 5U + 14U); + } else { + CM17FACCH1 facch11; + bool valid1 = facch11.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + if (valid1) + facch11.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch11.getRaw(netData + 5U + 0U); + + CM17FACCH1 facch12; + bool valid2 = facch12.decode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + if (valid2) + facch12.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + facch12.getRaw(netData + 5U + 14U); + } + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + scrambler(data + 2U); + + writeNetwork(netData, NNMT_VOICE_BODY); + +#if defined(DUMP_M17) + writeFile(data + 2U); +#endif + + if (m_duplex) + writeQueueRF(data); + + m_rfFrames++; + + m_display->writeM17RSSI(m_rssi); + } + + return true; +} + +bool CM17Control::processData(unsigned char option, unsigned char *data) +{ + CM17UDCH udch; + bool validUDCH = udch.decode(data + 2U); + if (m_rfState == RS_RF_LISTENING && !validUDCH) + return false; + + if (validUDCH) { + unsigned char ran = udch.getRAN(); + if (ran != m_ran && ran != 0U) + return false; + } + + unsigned char netData[40U]; + ::memset(netData, 0x00U, 40U); + + // The layer3 data will only be correct if valid is true + unsigned char buffer[23U]; + udch.getData(buffer); + + CM17Layer3 layer3; + layer3.decode(buffer, 184U); + + if (m_rfState == RS_RF_LISTENING) { + unsigned char type = layer3.getMessageType(); + if (type != M17_MESSAGE_TYPE_DCALL_HDR) + return false; + + unsigned short srcId = layer3.getSourceUnitId(); + unsigned short dstId = layer3.getDestinationGroupId(); + bool grp = layer3.getIsGroup(); + + if (m_selfOnly) { + if (srcId != m_id) + return false; + } + + unsigned char frames = layer3.getDataBlocks(); + + std::string source = m_lookup->find(srcId); + + m_display->writeM17(source.c_str(), grp, dstId, "R"); + m_display->writeM17RSSI(m_rssi); + + LogMessage("M17, received RF data header from %s to %s%u, %u blocks", source.c_str(), grp ? "TG " : "", dstId, frames); + + m_rfLayer3 = layer3; + m_rfFrames = 0U; + + m_rfState = RS_RF_DATA; + +#if defined(DUMP_M17) + openFile(); +#endif + } + + if (m_rfState != RS_RF_DATA) + return false; + + CSync::addM17Sync(data + 2U); + + CM17LICH lich; + lich.setRFCT(M17_LICH_RFCT_RDCH); + lich.setFCT(M17_LICH_USC_UDCH); + lich.setOption(option); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + lich.setDirection(M17_LICH_DIRECTION_INBOUND); + netData[0U] = lich.getRaw(); + + udch.getRaw(netData + 1U); + + unsigned char type = M17_MESSAGE_TYPE_DCALL_DATA; + + if (validUDCH) { + type = layer3.getMessageType(); + data[0U] = type == M17_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; + + udch.setRAN(m_ran); + udch.encode(data + 2U); + } else { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + } + + scrambler(data + 2U); + + switch (type) { + case M17_MESSAGE_TYPE_DCALL_HDR: + writeNetwork(netData, NNMT_DATA_HEADER); + break; + case M17_MESSAGE_TYPE_TX_REL: + writeNetwork(netData, NNMT_DATA_TRAILER); + break; + default: + writeNetwork(netData, NNMT_DATA_BODY); + break; + } + + if (m_duplex) + writeQueueRF(data); + + m_rfFrames++; + +#if defined(DUMP_M17) + writeFile(data + 2U); +#endif + + if (data[0U] == TAG_EOT) { + unsigned short dstId = m_rfLayer3.getDestinationGroupId(); + bool grp = m_rfLayer3.getIsGroup(); + std::string source = m_lookup->find(m_rfLayer3.getSourceUnitId()); + + LogMessage("M17, ended RF data transmission from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); + writeEndRF(); + } + + return true; +} + +unsigned int CM17Control::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 CM17Control::writeEndRF() +{ + m_rfState = RS_RF_LISTENING; + + m_rfMask = 0x00U; + m_rfLayer3.reset(); + + m_rfTimeoutTimer.stop(); + + if (m_netState == RS_NET_IDLE) { + m_display->clearM17(); + + if (m_network != NULL) + m_network->reset(); + } + +#if defined(DUMP_M17) + closeFile(); +#endif +} + +void CM17Control::writeEndNet() +{ + m_netState = RS_NET_IDLE; + + m_netMask = 0x00U; + m_netLayer3.reset(); + + m_netTimeoutTimer.stop(); + m_networkWatchdog.stop(); + m_packetTimer.stop(); + + m_display->clearM17(); + + if (m_network != NULL) + m_network->reset(); +} + +void CM17Control::writeNetwork() +{ + unsigned char netData[40U]; + bool exists = m_network->read(netData); + if (!exists) + return; + + if (!m_enabled) + return; + + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) + return; + + m_networkWatchdog.start(); + + unsigned char data[M17_FRAME_LENGTH_BYTES + 2U]; + + CSync::addM17Sync(data + 2U); + + CM17LICH lich; + lich.setRaw(netData[0U]); + unsigned char usc = lich.getFCT(); + unsigned char option = lich.getOption(); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(data + 2U); + + if (usc == M17_LICH_USC_UDCH) { + CM17Layer3 layer3; + layer3.setData(netData + 2U, 23U); + unsigned char type = layer3.getMessageType(); + + if (m_netState == RS_NET_IDLE) { + if (type == M17_MESSAGE_TYPE_DCALL_HDR) { + unsigned short srcId = layer3.getSourceUnitId(); + unsigned short dstId = layer3.getDestinationGroupId(); + bool grp = layer3.getIsGroup(); + + unsigned char frames = layer3.getDataBlocks(); + + std::string source = m_lookup->find(srcId); + m_display->writeM17(source.c_str(), grp, dstId, "N"); + LogMessage("M17, received network data header from %s to %s%u, %u blocks", source.c_str(), grp ? "TG " : "", dstId, frames); + + m_netState = RS_NET_DATA; + } else { + return; + } + } + + if (m_netState == RS_NET_DATA) { + data[0U] = type == M17_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + CM17UDCH udch; + udch.setRAN(m_ran); + udch.setData(netData + 2U); + udch.encode(data + 2U); + + scrambler(data + 2U); + + writeQueueNet(data); + + if (type == M17_MESSAGE_TYPE_TX_REL) { + unsigned short dstId = m_netLayer3.getDestinationGroupId(); + bool grp = m_netLayer3.getIsGroup(); + std::string source = m_lookup->find(m_netLayer3.getSourceUnitId()); + + LogMessage("M17, ended network data transmission from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId); + writeEndNet(); + } + } + } else if (usc == M17_LICH_USC_SACCH_NS) { + m_netLayer3.setData(netData + 5U + 0U, 10U); + + unsigned char type = m_netLayer3.getMessageType(); + if (type == M17_MESSAGE_TYPE_TX_REL && m_netState == RS_NET_IDLE) + return; + if (type == M17_MESSAGE_TYPE_VCALL && m_netState != RS_NET_IDLE) + return; + + CM17SACCH sacch; + sacch.setRAN(m_ran); + sacch.setStructure(M17_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(data + 2U); + + CM17FACCH1 facch; + facch.setRaw(netData + 5U + 0U); + facch.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + + data[0U] = type == M17_MESSAGE_TYPE_TX_REL ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + scrambler(data + 2U); + + writeQueueNet(data); + + unsigned short dstId = m_netLayer3.getDestinationGroupId(); + bool grp = m_netLayer3.getIsGroup(); + class CUserDBentry source; + m_lookup->findWithName(m_netLayer3.getSourceUnitId(), &source); + + if (type == M17_MESSAGE_TYPE_TX_REL) { + m_netFrames++; + LogMessage("M17, received network end of transmission from %s to %s%u, %.1f seconds", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId, float(m_netFrames) / 12.5F); + writeEndNet(); + } else if (type == M17_MESSAGE_TYPE_VCALL) { + LogMessage("M17, received network transmission from %s to %s%u", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId); + m_display->writeM17(source, grp, dstId, "N"); + + m_netTimeoutTimer.start(); + m_packetTimer.start(); + m_elapsed.start(); + m_netState = RS_NET_AUDIO; + m_netFrames = 1U; + } else { + CUtils::dump(2U, "M17, interesting non superblock network frame", netData, 33U); + return; + } + } else { + if (m_netState == RS_NET_IDLE) { + unsigned char structure = (netData[1U] >> 6) & 0x03U; + switch (structure) { + case M17_SR_1_4: + m_netLayer3.decode(netData + 2U, 18U, 0U); + if (m_netLayer3.getMessageType() == M17_MESSAGE_TYPE_VCALL) + m_netMask = 0x01U; + else + m_netMask = 0x00U; + break; + case M17_SR_2_4: + m_netMask |= 0x02U; + m_netLayer3.decode(netData + 2U, 18U, 18U); + break; + case M17_SR_3_4: + m_netMask |= 0x04U; + m_netLayer3.decode(netData + 2U, 18U, 36U); + break; + case M17_SR_4_4: + m_netMask |= 0x08U; + m_netLayer3.decode(netData + 2U, 18U, 54U); + break; + default: + break; + } + + if (m_netMask != 0x0FU) + return; + + unsigned char type = m_netLayer3.getMessageType(); + if (type != M17_MESSAGE_TYPE_VCALL) + return; + + unsigned short srcId = m_netLayer3.getSourceUnitId(); + unsigned short dstId = m_netLayer3.getDestinationGroupId(); + bool grp = m_netLayer3.getIsGroup(); + + class CUserDBentry source; + m_lookup->findWithName(srcId, &source); + LogMessage("M17, received network transmission from %s to %s%u", source.get(keyCALLSIGN).c_str(), grp ? "TG " : "", dstId); + m_display->writeM17(source, grp, dstId, "N"); + + m_netTimeoutTimer.start(); + m_packetTimer.start(); + m_elapsed.start(); + m_netState = RS_NET_AUDIO; + m_netFrames = 1U; + + // Create a dummy start message + unsigned char start[M17_FRAME_LENGTH_BYTES + 2U]; + + start[0U] = TAG_DATA; + start[1U] = 0x00U; + + // Generate the sync + CSync::addM17Sync(start + 2U); + + // Generate the LICH + CM17LICH lich; + lich.setRFCT(M17_LICH_RFCT_RDCH); + lich.setFCT(M17_LICH_USC_SACCH_NS); + lich.setOption(M17_LICH_STEAL_FACCH); + lich.setDirection(m_remoteGateway || !m_duplex ? M17_LICH_DIRECTION_INBOUND : M17_LICH_DIRECTION_OUTBOUND); + lich.encode(start + 2U); + + CM17SACCH sacch; + sacch.setRAN(m_ran); + sacch.setStructure(M17_SR_SINGLE); + sacch.setData(SACCH_IDLE); + sacch.encode(start + 2U); + + unsigned char message[22U]; + m_netLayer3.getData(message); + + CM17FACCH1 facch; + facch.setData(message); + facch.encode(start + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + facch.encode(start + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + + scrambler(start + 2U); + + writeQueueNet(start); + } + + m_netFrames++; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + CM17SACCH sacch; + sacch.setRaw(netData + 1U); + sacch.setRAN(m_ran); + sacch.encode(data + 2U); + + if (option == M17_LICH_STEAL_NONE) { + CM17Audio audio; + audio.encode(netData + 5U + 0U, data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + audio.encode(netData + 5U + 14U, data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + } else if (option == M17_LICH_STEAL_FACCH1_1) { + CM17FACCH1 facch1; + facch1.setRaw(netData + 5U + 0U); + facch1.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + + CM17Audio audio; + audio.encode(netData + 5U + 14U, data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 18U); + } else if (option == M17_LICH_STEAL_FACCH1_2) { + CM17Audio audio; + audio.encode(netData + 5U + 0U, data + 2U + M17_FSW_LICH_SACCH_LENGTH_BYTES + 0U); + + CM17FACCH1 facch1; + facch1.setRaw(netData + 5U + 14U); + facch1.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + } else { + CM17FACCH1 facch11; + facch11.setRaw(netData + 5U + 0U); + facch11.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS); + + CM17FACCH1 facch12; + facch12.setRaw(netData + 5U + 14U); + facch12.encode(data + 2U, M17_FSW_LENGTH_BITS + M17_LICH_LENGTH_BITS + M17_SACCH_LENGTH_BITS + M17_FACCH1_LENGTH_BITS); + } + + scrambler(data + 2U); + + writeQueueNet(data); + } +} + +void CM17Control::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("M17, network watchdog has expired, %.1f seconds", float(m_netFrames) / 12.5F); + writeEndNet(); + } + } +} + +void CM17Control::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 = M17_FRAME_LENGTH_BYTES + 2U; + + unsigned int space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError("M17, overflow in the M17 RF queue"); + return; + } + + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +void CM17Control::writeQueueNet(const unsigned char *data) +{ + assert(data != NULL); + + if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) + return; + + unsigned char len = M17_FRAME_LENGTH_BYTES + 2U; + + unsigned int space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError("M17, overflow in the M17 RF queue"); + return; + } + + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +void CM17Control::writeNetwork(const unsigned char *data, M17_NETWORK_MESSAGE_TYPE type) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) + return; + + m_network->write(data, type); +} + +void CM17Control::scrambler(unsigned char* data) const +{ + assert(data != NULL); + + for (unsigned int i = 0U; i < M17_FRAME_LENGTH_BYTES; i++) + data[i] ^= SCRAMBLER[i]; +} + +bool CM17Control::openFile() +{ + if (m_fp != NULL) + return true; + + time_t t; + ::time(&t); + + struct tm* tm = ::localtime(&t); + + char name[100U]; + ::sprintf(name, "M17_%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("M17", 1U, 3U, m_fp); + + return true; +} + +bool CM17Control::writeFile(const unsigned char* data) +{ + if (m_fp == NULL) + return false; + + ::fwrite(data, 1U, M17_FRAME_LENGTH_BYTES, m_fp); + + return true; +} + +void CM17Control::closeFile() +{ + if (m_fp != NULL) { + ::fclose(m_fp); + m_fp = NULL; + } +} + +bool CM17Control::isBusy() const +{ + return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE; +} + +void CM17Control::enable(bool enabled) +{ + if (!enabled && m_enabled) { + m_queue.clear(); + + // Reset the RF section + m_rfState = RS_RF_LISTENING; + + m_rfMask = 0x00U; + m_rfLayer3.reset(); + + m_rfTimeoutTimer.stop(); + + // Reset the networking section + m_netState = RS_NET_IDLE; + + m_netMask = 0x00U; + m_netLayer3.reset(); + + m_netTimeoutTimer.stop(); + m_networkWatchdog.stop(); + m_packetTimer.stop(); + } + + m_enabled = enabled; +} diff --git a/M17Control.h b/M17Control.h new file mode 100644 index 0000000..de7bc45 --- /dev/null +++ b/M17Control.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015-2020 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(M17Control_H) +#define M17Control_H + +#include "RSSIInterpolator.h" +#include "M17Network.h" +#include "M17Defines.h" +#include "RingBuffer.h" +#include "StopWatch.h" +#include "Display.h" +#include "Defines.h" +#include "Timer.h" +#include "Modem.h" + +#include + +class CM17Control { +public: + CM17Control(const std::string& callsign, bool selfOnly, CM17Network* network, CDisplay* display, unsigned int timeout, bool duplex, CRSSIInterpolator* rssiMapper); + ~CM17Control(); + + bool writeModem(unsigned char* data, unsigned int len); + + unsigned int readModem(unsigned char* data); + + void clock(unsigned int ms); + + bool isBusy() const; + + void enable(bool enabled); + +private: + std::string m_callsign; + bool m_selfOnly; + CM17Network* m_network; + CDisplay* m_display; + bool m_duplex; + 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_rfErrs; + unsigned int m_rfBits; + CNXDNLICH m_rfLastLICH; + CNXDNLayer3 m_rfLayer3; + CNXDNLayer3 m_netLayer3; + unsigned char m_rfMask; + unsigned char m_netMask; + CRSSIInterpolator* m_rssiMapper; + unsigned char m_rssi; + unsigned char m_maxRSSI; + unsigned char m_minRSSI; + unsigned int m_aveRSSI; + unsigned int m_rssiCount; + bool m_enabled; + FILE* m_fp; + + bool processVoice(unsigned char usc, unsigned char option, unsigned char* data); + + void writeQueueRF(const unsigned char* data); + void writeQueueNet(const unsigned char* data); + void writeNetwork(const unsigned char* data); + void writeNetwork(); + + void scrambler(unsigned char* data) const; + + void writeEndRF(); + void writeEndNet(); + + bool openFile(); + bool writeFile(const unsigned char* data); + void closeFile(); +}; + +#endif diff --git a/M17Defines.h b/M17Defines.h new file mode 100644 index 0000000..e9cb48f --- /dev/null +++ b/M17Defines.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016,2017,2018,2020 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(M17DEFINES_H) +#define M17DEFINES_H + +const unsigned int M17_RADIO_SYMBOL_LENGTH = 5U; // At 24 kHz sample rate + +const unsigned int M17_FRAME_LENGTH_BITS = 384U; +const unsigned int M17_FRAME_LENGTH_BYTES = M17_FRAME_LENGTH_BITS / 8U; +const unsigned int M17_FRAME_LENGTH_SYMBOLS = M17_FRAME_LENGTH_BITS / 2U; + +const unsigned int M17_SYNC_LENGTH_BITS = 16U; +const unsigned int M17_SYNC_LENGTH_SYMBOLS = M17_SYNC_LENGTH_BITS / 2U; + +const unsigned char M17_SYNC_BYTES[] = {0x32U, 0x43U}; +const unsigned int M17_SYNC_BYTES_LENGTH = 2U; + +#endif diff --git a/M17Network.cpp b/M17Network.cpp new file mode 100644 index 0000000..3a21373 --- /dev/null +++ b/M17Network.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2009-2014,2016,2019,2020 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. + */ + +#include "M17Network.h" +#include "M17Defines.h" +#include "Defines.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include + +const unsigned int BUFFER_LENGTH = 200U; + +CM17Network::CM17Network(unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, bool debug) : +m_socket(localPort), +m_addr(), +m_addrLen(0U), +m_debug(debug), +m_enabled(false), +m_buffer(1000U, "M17 Network") +{ + if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) + m_addrLen = 0U; +} + +CM17Network::~CM17Network() +{ +} + +bool CM17Network::open() +{ + if (m_addrLen == 0U) { + LogError("Unable to resolve the address of the M17 Gateway"); + return false; + } + + LogMessage("Opening M17 network connection"); + + return m_socket.open(m_addr); +} + +bool CM17Network::write(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[100U]; + + buffer[0U] = 'M'; + buffer[1U] = '1'; + buffer[2U] = '7'; + + if (m_debug) + CUtils::dump(1U, "M17 data transmitted", buffer, 36U); + + return m_socket.write(buffer, 36U, m_addr, m_addrLen); +} + +void CM17Network::clock(unsigned int ms) +{ + unsigned char buffer[BUFFER_LENGTH]; + + sockaddr_storage address; + unsigned int addrLen; + int length = m_socket.read(buffer, BUFFER_LENGTH, address, addrLen); + if (length <= 0) + return; + + if (!CUDPSocket::match(m_addr, address)) { + LogMessage("M17, packet received from an invalid source"); + return; + } + + if (!m_enabled) + return; + + if (m_debug) + CUtils::dump(1U, "M17 Network Data Received", buffer, length); + + if (::memcmp(buffer + 0U, "M17", 3U) != 0) + return; + + unsigned char c = length; + m_buffer.addData(&c, 1U); + + m_buffer.addData(buffer, length); +} + +bool CM17Network::read(unsigned char* data) +{ + assert(data != NULL); + + if (m_buffer.isEmpty()) + return false; + + unsigned char c = 0U; + m_buffer.getData(&c, 1U); + + m_buffer.getData(data, c); + + return true; +} + +void CM17Network::close() +{ + m_socket.close(); + + LogMessage("Closing M17 network connection"); +} + +void CM17Network::enable(bool enabled) +{ + if (!enabled && m_enabled) + m_buffer.clear(); + + m_enabled = enabled; +} diff --git a/M17Network.h b/M17Network.h new file mode 100644 index 0000000..d0ef7b5 --- /dev/null +++ b/M17Network.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 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. + */ + +#ifndef M17Network_H +#define M17Network_H + +#include "M17Defines.h" +#include "RingBuffer.h" +#include "UDPSocket.h" + +#include + +class CM17Network { +public: + CM17Network(unsigned int localPort, const std::string& gwyAddress, unsigned int gwyPort, bool debug); + ~CM17Network(); + + bool open(); + + void enable(bool enabled); + + bool write(const unsigned char* data); + + bool read(unsigned char* data); + + void reset(); + + void close(); + + void clock(unsigned int ms); + +private: + CUDPSocket m_socket; + sockaddr_storage m_addr; + unsigned int m_addrLen; + bool m_debug; + bool m_enabled; + CRingBuffer m_buffer; +}; + +#endif diff --git a/MMDVM.ini b/MMDVM.ini index 408e79b..6f51621 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -58,6 +58,7 @@ RFLevel=100 # YSFTXLevel=50 # P25TXLevel=50 # NXDNTXLevel=50 +# M17TXLevel-50 # POCSAGTXLevel=50 # FMTXLevel=50 RSSIMappingFile=RSSI.dat @@ -131,6 +132,12 @@ RemoteGateway=0 TXHang=5 # ModeHang=10 +[M17] +Enable=1 +SelfOnly=0 +TXHang=5 +# ModeHang=10 + [POCSAG] Enable=1 Frequency=439987500 @@ -215,6 +222,14 @@ GatewayPort=14020 # ModeHang=3 Debug=0 +[M17 Network] +Enable=1 +GatewayAddress=127.0.0.1 +GatewayPort=11657 +LocalPort=11657 +# ModeHang=3 +Debug=0 + [POCSAG Network] Enable=1 LocalAddress=127.0.0.1 diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index bfede16..6d8880a 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -120,12 +120,14 @@ m_dmr(NULL), m_ysf(NULL), m_p25(NULL), m_nxdn(NULL), +m_m17(NULL), m_pocsag(NULL), m_dstarNetwork(NULL), m_dmrNetwork(NULL), m_ysfNetwork(NULL), m_p25Network(NULL), m_nxdnNetwork(NULL), +m_m17Network(NULL), m_pocsagNetwork(NULL), m_display(NULL), m_ump(NULL), @@ -135,11 +137,13 @@ m_dmrRFModeHang(10U), m_ysfRFModeHang(10U), m_p25RFModeHang(10U), m_nxdnRFModeHang(10U), +m_m17RFModeHang(10U), m_dstarNetModeHang(3U), m_dmrNetModeHang(3U), m_ysfNetModeHang(3U), m_p25NetModeHang(3U), m_nxdnNetModeHang(3U), +m_m17NetModeHang(3U), m_pocsagNetModeHang(3U), m_modeTimer(1000U), m_dmrTXTimer(1000U), @@ -151,6 +155,7 @@ m_dmrEnabled(false), m_ysfEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), +m_m17Enabled(false), m_pocsagEnabled(false), m_fmEnabled(false), m_cwIdTime(0U), @@ -317,6 +322,12 @@ int CMMDVMHost::run() return 1; } + if (m_m17Enabled && m_conf.getM17NetworkEnabled()) { + ret = createM17Network(); + if (!ret) + return 1; + } + if (m_pocsagEnabled && m_conf.getPOCSAGNetworkEnabled()) { ret = createPOCSAGNetwork(); if (!ret) @@ -496,7 +507,6 @@ int CMMDVMHost::run() else if (ovcm == DMR_OVCM_ON) LogInfo(" OVCM: on"); - switch (dmrBeacons) { case DMR_BEACONS_NETWORK: { unsigned int dmrBeaconDuration = m_conf.getDMRBeaconDuration(); @@ -599,6 +609,19 @@ int CMMDVMHost::run() m_nxdn = new CNXDNControl(ran, id, selfOnly, m_nxdnNetwork, m_display, m_timeout, m_duplex, remoteGateway, m_nxdnLookup, rssi); } + if (m_m17Enabled) { + bool selfOnly = m_conf.getM17SelfOnly(); + unsigned int txHang = m_conf.getM17TXHang(); + m_m17RFModeHang = m_conf.getM17ModeHang(); + + LogInfo("M17 RF Parameters"); + LogInfo(" Self Only: %s", selfOnly ? "yes" : "no"); + LogInfo(" TX Hang: %us", txHang); + LogInfo(" Mode Hang: %us", m_m17RFModeHang); + + m_m17 = new CM17Control(m_callsign, selfOnly, m_m17Network, m_display, m_timeout, m_duplex, rssi); + } + CTimer pocsagTimer(1000U, 30U); if (m_pocsagEnabled) { @@ -807,6 +830,22 @@ int CMMDVMHost::run() } } + len = m_modem->readM17Data(data); + if (m_m17 != NULL && len > 0U) { + if (m_mode == MODE_IDLE) { + bool ret = m_m17->writeModem(data, len); + if (ret) { + m_modeTimer.setTimeout(m_m17RFModeHang); + setMode(MODE_M17); + } + } else if (m_mode == MODE_M17) { + m_m17->writeModem(data, len); + m_modeTimer.start(); + } else if (m_mode != MODE_LOCKOUT) { + LogWarning("M17 modem data received when in mode %u", m_mode); + } + } + len = m_modem->readTransparentData(data); if (transparentSocket != NULL && len > 0U) transparentSocket->write(data, len, transparentAddress, transparentAddrLen); @@ -938,6 +977,25 @@ int CMMDVMHost::run() } } + if (m_m17 != NULL) { + ret = m_modem->hasM17Space(); + if (ret) { + len = m_m17->readModem(data); + if (len > 0U) { + if (m_mode == MODE_IDLE) { + m_modeTimer.setTimeout(m_m17NetModeHang); + setMode(MODE_M17); + } + if (m_mode == MODE_M17) { + m_modem->writeM17Data(data, len); + m_modeTimer.start(); + } else if (m_mode != MODE_LOCKOUT) { + LogWarning("M17 data received when in mode %u", m_mode); + } + } + } + } + if (m_pocsag != NULL) { ret = m_modem->hasPOCSAGSpace(); if (ret) { @@ -987,6 +1045,8 @@ int CMMDVMHost::run() m_p25->clock(ms); if (m_nxdn != NULL) m_nxdn->clock(ms); + if (m_m17 != NULL) + m_m17->clock(ms); if (m_pocsag != NULL) m_pocsag->clock(ms); @@ -1000,6 +1060,8 @@ int CMMDVMHost::run() m_p25Network->clock(ms); if (m_nxdnNetwork != NULL) m_nxdnNetwork->clock(ms); + if (m_m17Network != NULL) + m_m17Network->clock(ms); if (m_pocsagNetwork != NULL) m_pocsagNetwork->clock(ms); @@ -1114,6 +1176,11 @@ int CMMDVMHost::run() delete m_nxdnNetwork; } + if (m_m17Network != NULL) { + m_m17Network->close(); + delete m_m17Network; + } + if (m_pocsagNetwork != NULL) { m_pocsagNetwork->close(); delete m_pocsagNetwork; @@ -1134,6 +1201,7 @@ int CMMDVMHost::run() delete m_ysf; delete m_p25; delete m_nxdn; + delete m_m17; delete m_pocsag; return 0; @@ -1156,6 +1224,7 @@ bool CMMDVMHost::createModem() float ysfTXLevel = m_conf.getModemYSFTXLevel(); float p25TXLevel = m_conf.getModemP25TXLevel(); float nxdnTXLevel = m_conf.getModemNXDNTXLevel(); + float m17TXLevel = m_conf.getModemM17TXLevel(); float pocsagTXLevel = m_conf.getModemPOCSAGTXLevel(); float fmTXLevel = m_conf.getModemFMTXLevel(); bool trace = m_conf.getModemTrace(); @@ -1165,6 +1234,7 @@ bool CMMDVMHost::createModem() unsigned int ysfTXHang = m_conf.getFusionTXHang(); unsigned int p25TXHang = m_conf.getP25TXHang(); unsigned int nxdnTXHang = m_conf.getNXDNTXHang(); + unsigned int m17TXHang = m_conf.getM17TXHang(); unsigned int rxFrequency = m_conf.getRXFrequency(); unsigned int txFrequency = m_conf.getTXFrequency(); unsigned int pocsagFrequency = m_conf.getPOCSAGFrequency(); @@ -1197,6 +1267,7 @@ bool CMMDVMHost::createModem() LogInfo(" YSF TX Level: %.1f%%", ysfTXLevel); LogInfo(" P25 TX Level: %.1f%%", p25TXLevel); LogInfo(" NXDN TX Level: %.1f%%", nxdnTXLevel); + LogInfo(" M17 TX Level: %.1f%%", m17TXLevel); LogInfo(" POCSAG TX Level: %.1f%%", pocsagTXLevel); LogInfo(" FM TX Level: %.1f%%", fmTXLevel); LogInfo(" TX Frequency: %uHz (%uHz)", txFrequency, txFrequency + txOffset); @@ -1204,13 +1275,14 @@ bool CMMDVMHost::createModem() m_modem = CModem::createModem(port, m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); m_modem->setSerialParams(protocol, address); - m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_pocsagEnabled, m_fmEnabled); - m_modem->setLevels(rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel); + m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_m17Enabled, m_pocsagEnabled, m_fmEnabled); + m_modem->setLevels(rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel); m_modem->setRFParams(rxFrequency, rxOffset, txFrequency, txOffset, txDCOffset, rxDCOffset, rfLevel, pocsagFrequency); m_modem->setDMRParams(colorCode); m_modem->setYSFParams(lowDeviation, ysfTXHang); m_modem->setP25Params(p25TXHang); m_modem->setNXDNParams(nxdnTXHang); + m_modem->setM17Params(m17TXHang); if (m_fmEnabled) { std::string callsign = m_conf.getFMCallsign(); @@ -1465,6 +1537,33 @@ bool CMMDVMHost::createNXDNNetwork() return true; } +bool CMMDVMHost::createM17Network() +{ + std::string gatewayAddress = m_conf.getM17GatewayAddress(); + unsigned int gatewayPort = m_conf.getM17GatewayPort(); + unsigned int localPort = m_conf.getM17LocalPort(); + m_m17NetModeHang = m_conf.getM17NetworkModeHang(); + bool debug = m_conf.getM17NetworkDebug(); + + LogInfo("M17 Network Parameters"); + LogInfo(" Gateway Address: %s", gatewayAddress.c_str()); + LogInfo(" Gateway Port: %u", gatewayPort); + LogInfo(" Local Port: %u", localPort); + LogInfo(" Mode Hang: %us", m_m17NetModeHang); + + m_m17Network = new CM17Network(localPort, gatewayAddress, gatewayPort, debug); + bool ret = m_m17Network->open(); + if (!ret) { + delete m_m17Network; + m_m17Network = NULL; + return false; + } + + m_m17Network->enable(true); + + return true; +} + bool CMMDVMHost::createPOCSAGNetwork() { std::string gatewayAddress = m_conf.getPOCSAGGatewayAddress(); @@ -1502,6 +1601,7 @@ void CMMDVMHost::readParams() m_ysfEnabled = m_conf.getFusionEnabled(); m_p25Enabled = m_conf.getP25Enabled(); m_nxdnEnabled = m_conf.getNXDNEnabled(); + m_m17Enabled = m_conf.getM17Enabled(); m_pocsagEnabled = m_conf.getPOCSAGEnabled(); m_fmEnabled = m_conf.getFMEnabled(); m_duplex = m_conf.getDuplex(); @@ -1519,6 +1619,7 @@ void CMMDVMHost::readParams() LogInfo(" YSF: %s", m_ysfEnabled ? "enabled" : "disabled"); LogInfo(" P25: %s", m_p25Enabled ? "enabled" : "disabled"); LogInfo(" NXDN: %s", m_nxdnEnabled ? "enabled" : "disabled"); + LogInfo(" M17: %s", m_m17Enabled ? "enabled" : "disabled"); LogInfo(" POCSAG: %s", m_pocsagEnabled ? "enabled" : "disabled"); LogInfo(" FM: %s", m_fmEnabled ? "enabled" : "disabled"); } @@ -1540,6 +1641,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1552,6 +1655,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); m_modem->setMode(MODE_DSTAR); @@ -1574,6 +1679,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1586,6 +1693,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); m_modem->setMode(MODE_DMR); @@ -1612,6 +1721,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1624,6 +1735,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); m_modem->setMode(MODE_YSF); @@ -1646,6 +1759,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(true); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1658,6 +1773,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(true); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); m_modem->setMode(MODE_P25); @@ -1680,6 +1797,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(true); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1692,6 +1811,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(true); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); m_modem->setMode(MODE_NXDN); @@ -1703,6 +1824,44 @@ void CMMDVMHost::setMode(unsigned char mode) createLockFile("NXDN"); break; + case MODE_M17: + 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); + if (m_nxdnNetwork != NULL) + m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(true); + if (m_pocsagNetwork != NULL) + m_pocsagNetwork->enable(false); + if (m_dstar != NULL) + m_dstar->enable(false); + if (m_dmr != NULL) + m_dmr->enable(false); + if (m_ysf != NULL) + m_ysf->enable(false); + if (m_p25 != NULL) + m_p25->enable(false); + if (m_nxdn != NULL) + m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(true); + if (m_pocsag != NULL) + m_pocsag->enable(false); + m_modem->setMode(MODE_M17); + if (m_ump != NULL) + m_ump->setMode(MODE_M17); + m_mode = MODE_M17; + m_modeTimer.start(); + m_cwIdTimer.stop(); + createLockFile("M17"); + break; + case MODE_POCSAG: if (m_dstarNetwork != NULL) m_dstarNetwork->enable(false); @@ -1714,6 +1873,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(true); if (m_dstar != NULL) @@ -1726,6 +1887,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(true); m_modem->setMode(MODE_POCSAG); @@ -1748,6 +1911,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1760,6 +1925,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { @@ -1786,6 +1953,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1798,6 +1967,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { @@ -1826,6 +1997,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(false); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); + if (m_m17Network != NULL) + m_m17Network->enable(false); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(false); if (m_dstar != NULL) @@ -1838,6 +2011,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(false); if (m_nxdn != NULL) m_nxdn->enable(false); + if (m_m17 != NULL) + m_m17->enable(false); if (m_pocsag != NULL) m_pocsag->enable(false); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { @@ -1864,6 +2039,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25Network->enable(true); if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(true); + if (m_m17Network != NULL) + m_m17Network->enable(true); if (m_pocsagNetwork != NULL) m_pocsagNetwork->enable(true); if (m_dstar != NULL) @@ -1876,6 +2053,8 @@ void CMMDVMHost::setMode(unsigned char mode) m_p25->enable(true); if (m_nxdn != NULL) m_nxdn->enable(true); + if (m_m17 != NULL) + m_m17->enable(true); if (m_pocsag != NULL) m_pocsag->enable(true); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { @@ -1955,6 +2134,10 @@ void CMMDVMHost::remoteControl() if (m_nxdn != NULL) processModeCommand(MODE_NXDN, m_nxdnRFModeHang); break; + case RCD_MODE_M17: + if (m_m17 != NULL) + processModeCommand(MODE_M17, m_m17RFModeHang); + break; case RCD_MODE_FM: if (m_fmEnabled != false) processModeCommand(MODE_FM, 0); @@ -1989,6 +2172,12 @@ void CMMDVMHost::remoteControl() if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(true); break; + case RCD_ENABLE_M17: + if (m_m17 != NULL && m_m17Enabled == false) + processEnableCommand(m_m17Enabled, true); + if (m_m17Network != NULL) + m_m17Network->enable(true); + break; case RCD_ENABLE_FM: if (m_fmEnabled==false) processEnableCommand(m_fmEnabled, true); @@ -2023,6 +2212,12 @@ void CMMDVMHost::remoteControl() if (m_nxdnNetwork != NULL) m_nxdnNetwork->enable(false); break; + case RCD_DISABLE_M17: + if (m_m17 != NULL && m_m17Enabled == true) + processEnableCommand(m_m17Enabled, false); + if (m_m17Network != NULL) + m_m17Network->enable(false); + break; case RCD_DISABLE_FM: if (m_fmEnabled == true) processEnableCommand(m_fmEnabled, false); @@ -2038,18 +2233,20 @@ void CMMDVMHost::remoteControl() } m_pocsag->sendPage(ric, text); } + break; case RCD_CW: setMode(MODE_IDLE); // Force the modem to go idle so that we can send the CW text. - if (!m_modem->hasTX()){ - std::string cwtext; - for (unsigned int i = 0U; i < m_remoteControl->getArgCount(); i++) { - if (i > 0U) - cwtext += " "; - cwtext += m_remoteControl->getArgString(i); - } - m_display->writeCW(); - m_modem->sendCWId(cwtext); - } + if (!m_modem->hasTX()){ + std::string cwtext; + for (unsigned int i = 0U; i < m_remoteControl->getArgCount(); i++) { + if (i > 0U) + cwtext += " "; + cwtext += m_remoteControl->getArgString(i); + } + m_display->writeCW(); + m_modem->sendCWId(cwtext); + } + break; default: break; } @@ -2075,9 +2272,11 @@ void CMMDVMHost::processModeCommand(unsigned char mode, unsigned int timeout) void CMMDVMHost::processEnableCommand(bool& mode, bool enabled) { - LogDebug("Setting mode current=%s new=%s",mode ? "true" : "false",enabled ? "true" : "false"); - mode=enabled; - m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_pocsagEnabled, m_fmEnabled); + LogDebug("Setting mode current=%s new=%s", mode ? "true" : "false", enabled ? "true" : "false"); + + mode = enabled; + + m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_m17Enabled, m_pocsagEnabled, m_fmEnabled); if (!m_modem->writeConfig()) LogError("Cannot write Config to MMDVM"); } diff --git a/MMDVMHost.h b/MMDVMHost.h index 4c58f98..9fadcb9 100644 --- a/MMDVMHost.h +++ b/MMDVMHost.h @@ -29,10 +29,12 @@ #include "YSFControl.h" #include "P25Control.h" #include "NXDNControl.h" +#include "M17Control.h" #include "NXDNLookup.h" #include "YSFNetwork.h" #include "P25Network.h" #include "DMRNetwork.h" +#include "M17Network.h" #include "DMRLookup.h" #include "Display.h" #include "Timer.h" @@ -59,12 +61,14 @@ private: CYSFControl* m_ysf; CP25Control* m_p25; CNXDNControl* m_nxdn; + CM17Control* m_m17; CPOCSAGControl* m_pocsag; CDStarNetwork* m_dstarNetwork; CDMRNetwork* m_dmrNetwork; CYSFNetwork* m_ysfNetwork; CP25Network* m_p25Network; INXDNNetwork* m_nxdnNetwork; + CM17Network* m_m17Network; CPOCSAGNetwork* m_pocsagNetwork; CDisplay* m_display; CUMP* m_ump; @@ -74,11 +78,13 @@ private: unsigned int m_ysfRFModeHang; unsigned int m_p25RFModeHang; unsigned int m_nxdnRFModeHang; + unsigned int m_m17RFModeHang; unsigned int m_dstarNetModeHang; unsigned int m_dmrNetModeHang; unsigned int m_ysfNetModeHang; unsigned int m_p25NetModeHang; unsigned int m_nxdnNetModeHang; + unsigned int m_m17NetModeHang; unsigned int m_pocsagNetModeHang; CTimer m_modeTimer; CTimer m_dmrTXTimer; @@ -90,6 +96,7 @@ private: bool m_ysfEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_m17Enabled; bool m_pocsagEnabled; bool m_fmEnabled; unsigned int m_cwIdTime; @@ -110,6 +117,7 @@ private: bool createYSFNetwork(); bool createP25Network(); bool createNXDNNetwork(); + bool createM17Network(); bool createPOCSAGNetwork(); void remoteControl(); diff --git a/MMDVMHost.vcxproj b/MMDVMHost.vcxproj index ef7577c..317803a 100644 --- a/MMDVMHost.vcxproj +++ b/MMDVMHost.vcxproj @@ -188,6 +188,9 @@ + + + @@ -283,6 +286,8 @@ + + diff --git a/MMDVMHost.vcxproj.filters b/MMDVMHost.vcxproj.filters index ee1ff1b..eec7ccb 100644 --- a/MMDVMHost.vcxproj.filters +++ b/MMDVMHost.vcxproj.filters @@ -299,6 +299,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -562,5 +571,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/Modem.cpp b/Modem.cpp index 52a497d..2451c9b 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -23,6 +23,7 @@ #include "P25Defines.h" #include "NXDNDefines.h" #include "POCSAGDefines.h" +#include "M17Defines.h" #include "Thread.h" #include "Modem.h" #include "NullModem.h" @@ -74,6 +75,9 @@ 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_M17_DATA = 0x45U; +const unsigned char MMDVM_M17_LOST = 0x46U; + const unsigned char MMDVM_POCSAG_DATA = 0x50U; const unsigned char MMDVM_FM_PARAMS1 = 0x60U; @@ -106,6 +110,7 @@ m_ysfLoDev(false), m_ysfTXHang(4U), m_p25TXHang(5U), m_nxdnTXHang(5U), +m_m17TXHang(5U), m_duplex(duplex), m_rxInvert(rxInvert), m_txInvert(txInvert), @@ -119,6 +124,7 @@ m_dmrTXLevel(0.0F), m_ysfTXLevel(0.0F), m_p25TXLevel(0.0F), m_nxdnTXLevel(0.0F), +m_m17TXLevel(0.0F), m_pocsagTXLevel(0.0F), m_fmTXLevel(0.0F), m_rfLevel(0.0F), @@ -133,6 +139,7 @@ m_dmrEnabled(false), m_ysfEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), +m_m17Enabled(false), m_pocsagEnabled(false), m_fmEnabled(false), m_rxDCOffset(0), @@ -153,6 +160,8 @@ 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_rxM17Data(1000U, "Modem RX M17"), +m_txM17Data(1000U, "Modem TX M17"), m_txPOCSAGData(1000U, "Modem TX POCSAG"), m_rxTransparentData(1000U, "Modem RX Transparent"), m_txTransparentData(1000U, "Modem TX Transparent"), @@ -166,6 +175,7 @@ m_dmrSpace2(0U), m_ysfSpace(0U), m_p25Space(0U), m_nxdnSpace(0U), +m_m17Space(0U), m_pocsagSpace(0U), m_tx(false), m_cd(false), @@ -232,18 +242,19 @@ void CModem::setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int tx m_pocsagFrequency = pocsagFrequency + txOffset; } -void CModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool pocsagEnabled, bool fmEnabled) +void CModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool m17Enabled, bool pocsagEnabled, bool fmEnabled) { m_dstarEnabled = dstarEnabled; m_dmrEnabled = dmrEnabled; m_ysfEnabled = ysfEnabled; m_p25Enabled = p25Enabled; m_nxdnEnabled = nxdnEnabled; + m_m17Enabled = m17Enabled; m_pocsagEnabled = pocsagEnabled; m_fmEnabled = fmEnabled; } -void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float pocsagTXLevel, float fmTXLevel) +void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagTXLevel, float fmTXLevel) { m_rxLevel = rxLevel; m_cwIdTXLevel = cwIdTXLevel; @@ -252,6 +263,7 @@ void CModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, flo m_ysfTXLevel = ysfTXLevel; m_p25TXLevel = p25TXLevel; m_nxdnTXLevel = nxdnTXLevel; + m_m17TXLevel = m17TXLevel; m_pocsagTXLevel = pocsagTXLevel; m_fmTXLevel = fmTXLevel; } @@ -279,6 +291,11 @@ void CModem::setNXDNParams(unsigned int txHang) m_nxdnTXHang = txHang; } +void CModem::setM17Params(unsigned int txHang) +{ + m_m17TXHang = txHang; +} + void CModem::setTransparentDataParams(unsigned int sendFrameType) { m_sendTransparentDataFrameType = sendFrameType; @@ -587,12 +604,39 @@ void CModem::clock(unsigned int ms) } break; + case MMDVM_M17_DATA: { + if (m_trace) + CUtils::dump(1U, "RX M17 Data", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxM17Data.addData(&data, 1U); + + data = TAG_DATA; + m_rxM17Data.addData(&data, 1U); + + m_rxM17Data.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_M17_LOST: { + if (m_trace) + CUtils::dump(1U, "RX M17 Lost", m_buffer, m_length); + + unsigned char data = 1U; + m_rxM17Data.addData(&data, 1U); + + data = TAG_LOST; + m_rxM17Data.addData(&data, 1U); + } + break; + case MMDVM_GET_STATUS: { // if (m_trace) // CUtils::dump(1U, "GET_STATUS", m_buffer, m_length); m_p25Space = 0U; m_nxdnSpace = 0U; + m_m17Space = 0U; m_pocsagSpace = 0U; m_mode = m_buffer[4U]; @@ -630,9 +674,11 @@ void CModem::clock(unsigned int ms) m_nxdnSpace = m_buffer[11U]; if (m_length > 12U) m_pocsagSpace = m_buffer[12U]; + if (m_length > 13U) + m_m17Space = m_buffer[13U]; m_inactivityTimer.start(); - // LogMessage("status=%02X, tx=%d, space=%u,%u,%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, m_nxdnSpace, m_pocsagSpace, int(m_lockout), int(m_cd)); + // LogMessage("status=%02X, tx=%d, space=%u,%u,%u,%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, m_nxdnSpace, m_m17Space, m_pocsagSpace, int(m_lockout), int(m_cd)); } break; @@ -819,6 +865,23 @@ void CModem::clock(unsigned int ms) m_nxdnSpace--; } + if (m_m17Space > 1U && !m_txM17Data.isEmpty()) { + unsigned char len = 0U; + m_txM17Data.getData(&len, 1U); + m_txM17Data.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX M17 Data", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing M17 data to the MMDVM"); + + m_playoutTimer.start(); + + m_m17Space--; + } + if (m_pocsagSpace > 1U && !m_txPOCSAGData.isEmpty()) { unsigned char len = 0U; m_txPOCSAGData.getData(&len, 1U); @@ -943,6 +1006,20 @@ unsigned int CModem::readNXDNData(unsigned char* data) return len; } +unsigned int CModem::readM17Data(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxM17Data.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxM17Data.getData(&len, 1U); + m_rxM17Data.getData(data, len); + + return len; +} + unsigned int CModem::readTransparentData(unsigned char* data) { assert(data != NULL); @@ -1157,6 +1234,36 @@ bool CModem::writeNXDNData(const unsigned char* data, unsigned int length) return true; } +bool CModem::hasM17Space() const +{ + unsigned int space = m_txM17Data.freeSpace() / (M17_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeM17Data(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_M17_DATA; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txM17Data.addData(&len, 1U); + m_txM17Data.addData(buffer, len); + + return true; +} + bool CModem::hasPOCSAGSpace() const { unsigned int space = m_txPOCSAGData.freeSpace() / (POCSAG_FRAME_LENGTH_BYTES + 4U); @@ -1351,6 +1458,30 @@ bool CModem::writeNXDNInfo(const char* source, bool group, unsigned int dest, co return m_serial->write(buffer, 31U) != 31; } +bool CModem::writeM17Info(const char* source, const char* dest, const char* type) +{ + assert(m_serial != NULL); + assert(source != NULL); + assert(dest != NULL); + assert(type != NULL); + + unsigned char buffer[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 31U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_M17; + + ::sprintf((char*)(buffer + 4U), "%9.9s", source); + + ::sprintf((char*)(buffer + 13U), "%9.9s", dest); + + ::memcpy(buffer + 22U, type, 1U); + + return m_serial->write(buffer, 23U) != 23; +} + bool CModem::writePOCSAGInfo(unsigned int ric, const std::string& message) { assert(m_serial != NULL); @@ -1523,7 +1654,7 @@ bool CModem::setConfig() buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 24U; + buffer[1U] = 26U; buffer[2U] = MMDVM_SET_CONFIG; @@ -1558,6 +1689,8 @@ bool CModem::setConfig() buffer[4U] |= 0x20U; if (m_fmEnabled && m_duplex) buffer[4U] |= 0x40U; + if (m_m17Enabled) + buffer[4U] |= 0x80U; buffer[5U] = m_txDelay / 10U; // In 10ms units @@ -1593,10 +1726,13 @@ bool CModem::setConfig() buffer[23U] = (unsigned char)m_nxdnTXHang; - // CUtils::dump(1U, "Written", buffer, 24U); + buffer[24U] = (unsigned char)(m_m17TXLevel * 2.55F + 0.5F); + buffer[25U] = (unsigned char)m_m17TXHang; - int ret = m_serial->write(buffer, 24U); - if (ret != 24) + // CUtils::dump(1U, "Written", buffer, 26U); + + int ret = m_serial->write(buffer, 26U); + if (ret != 26) return false; unsigned int count = 0U; diff --git a/Modem.h b/Modem.h index e28aad4..8c5e53a 100644 --- a/Modem.h +++ b/Modem.h @@ -39,12 +39,13 @@ public: virtual void setSerialParams(const std::string& protocol, unsigned int address); virtual void setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int txFrequency, int txOffset, int txDCOffset, int rxDCOffset, float rfLevel, unsigned int pocsagFrequency); - virtual void setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool pocsagEnabled, bool fmEnabled); - virtual void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float pocsagLevel, float fmTXLevel); + virtual void setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool m17Enabled, bool pocsagEnabled, bool fmEnabled); + virtual void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagLevel, float fmTXLevel); virtual void setDMRParams(unsigned int colorCode); virtual void setYSFParams(bool loDev, unsigned int txHang); virtual void setP25Params(unsigned int txHang); virtual void setNXDNParams(unsigned int txHang); + virtual void setM17Params(unsigned int txHang); virtual void setTransparentDataParams(unsigned int sendFrameType); virtual void setFMCallsignParams(const std::string& callsign, unsigned int callsignSpeed, unsigned int callsignFrequency, unsigned int callsignTime, unsigned int callsignHoldoff, float callsignHighLevel, float callsignLowLevel, bool callsignAtStart, bool callsignAtEnd, bool callsignAtLatch); @@ -59,6 +60,7 @@ public: virtual unsigned int readYSFData(unsigned char* data); virtual unsigned int readP25Data(unsigned char* data); virtual unsigned int readNXDNData(unsigned char* data); + virtual unsigned int readM17Data(unsigned char* data); virtual unsigned int readTransparentData(unsigned char* data); virtual unsigned int readSerial(unsigned char* data, unsigned int length); @@ -69,6 +71,7 @@ public: virtual bool hasYSFSpace() const; virtual bool hasP25Space() const; virtual bool hasNXDNSpace() const; + virtual bool hasM17Space() const; virtual bool hasPOCSAGSpace() const; virtual bool hasTX() const; @@ -84,6 +87,7 @@ public: virtual bool writeYSFData(const unsigned char* data, unsigned int length); virtual bool writeP25Data(const unsigned char* data, unsigned int length); virtual bool writeNXDNData(const unsigned char* data, unsigned int length); + virtual bool writeM17Data(const unsigned char* data, unsigned int length); virtual bool writePOCSAGData(const unsigned char* data, unsigned int length); virtual bool writeTransparentData(const unsigned char* data, unsigned int length); @@ -93,6 +97,7 @@ public: virtual bool writeYSFInfo(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin); virtual bool writeP25Info(const char* source, bool group, unsigned int dest, const char* type); virtual bool writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type); + virtual bool writeM17Info(const char* source, const char* dest, const char* type); virtual bool writePOCSAGInfo(unsigned int ric, const std::string& message); virtual bool writeIPInfo(const std::string& address); @@ -122,6 +127,7 @@ private: unsigned int m_ysfTXHang; unsigned int m_p25TXHang; unsigned int m_nxdnTXHang; + unsigned int m_m17TXHang; bool m_duplex; bool m_rxInvert; bool m_txInvert; @@ -135,6 +141,7 @@ private: float m_ysfTXLevel; float m_p25TXLevel; float m_nxdnTXLevel; + float m_m17TXLevel; float m_pocsagTXLevel; float m_fmTXLevel; float m_rfLevel; @@ -149,6 +156,7 @@ private: bool m_ysfEnabled; bool m_p25Enabled; bool m_nxdnEnabled; + bool m_m17Enabled; bool m_pocsagEnabled; bool m_fmEnabled; int m_rxDCOffset; @@ -169,6 +177,8 @@ private: CRingBuffer m_txP25Data; CRingBuffer m_rxNXDNData; CRingBuffer m_txNXDNData; + CRingBuffer m_rxM17Data; + CRingBuffer m_txM17Data; CRingBuffer m_txPOCSAGData; CRingBuffer m_rxTransparentData; CRingBuffer m_txTransparentData; @@ -182,6 +192,7 @@ private: unsigned int m_ysfSpace; unsigned int m_p25Space; unsigned int m_nxdnSpace; + unsigned int m_m17Space; unsigned int m_pocsagSpace; bool m_tx; bool m_cd; diff --git a/RemoteControl.cpp b/RemoteControl.cpp index 3c2d2a8..2e3bf8e 100644 --- a/RemoteControl.cpp +++ b/RemoteControl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Jonathan Naylor G4KLX + * Copyright (C) 2019,2020 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 @@ -89,6 +89,8 @@ REMOTE_COMMAND CRemoteControl::getCommand() m_command = RCD_MODE_P25; else if (m_args.at(1U) == "nxdn") m_command = RCD_MODE_NXDN; + else if (m_args.at(1U) == "m17") + m_command = RCD_MODE_M17; } else if (m_args.at(0U) == "enable" && m_args.size() >= ENABLE_ARGS) { if (m_args.at(1U) == "dstar") m_command = RCD_ENABLE_DSTAR; @@ -100,6 +102,8 @@ REMOTE_COMMAND CRemoteControl::getCommand() m_command = RCD_ENABLE_P25; else if (m_args.at(1U) == "nxdn") m_command = RCD_ENABLE_NXDN; + else if (m_args.at(1U) == "m17") + m_command = RCD_ENABLE_M17; else if (m_args.at(1U) == "fm") m_command = RCD_ENABLE_FM; } else if (m_args.at(0U) == "disable" && m_args.size() >= DISABLE_ARGS) { @@ -113,6 +117,8 @@ REMOTE_COMMAND CRemoteControl::getCommand() m_command = RCD_DISABLE_P25; else if (m_args.at(1U) == "nxdn") m_command = RCD_DISABLE_NXDN; + else if (m_args.at(1U) == "m17") + m_command = RCD_DISABLE_M17; else if (m_args.at(1U) == "fm") m_command = RCD_DISABLE_FM; } else if (m_args.at(0U) == "page" && m_args.size() >= PAGE_ARGS) { @@ -144,6 +150,7 @@ unsigned int CRemoteControl::getArgCount() const case RCD_MODE_YSF: case RCD_MODE_P25: case RCD_MODE_NXDN: + case RCD_MODE_M17: return m_args.size() - SET_MODE_ARGS; case RCD_PAGE: return m_args.size() - 1U; @@ -164,14 +171,15 @@ std::string CRemoteControl::getArgString(unsigned int n) const case RCD_MODE_YSF: case RCD_MODE_P25: case RCD_MODE_NXDN: + case RCD_MODE_M17: n += SET_MODE_ARGS; break; case RCD_PAGE: n += 1U; break; case RCD_CW: - n += 1U; - break; + n += 1U; + break; default: return ""; } diff --git a/RemoteControl.h b/RemoteControl.h index c8d060d..ace1425 100644 --- a/RemoteControl.h +++ b/RemoteControl.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Jonathan Naylor G4KLX + * Copyright (C) 2019,2020 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 @@ -33,18 +33,21 @@ enum REMOTE_COMMAND { RCD_MODE_YSF, RCD_MODE_P25, RCD_MODE_NXDN, + RCD_MODE_M17, RCD_MODE_FM, RCD_ENABLE_DSTAR, RCD_ENABLE_DMR, RCD_ENABLE_YSF, RCD_ENABLE_P25, RCD_ENABLE_NXDN, + RCD_ENABLE_M17, RCD_ENABLE_FM, RCD_DISABLE_DSTAR, RCD_DISABLE_DMR, RCD_DISABLE_YSF, RCD_DISABLE_P25, RCD_DISABLE_NXDN, + RCD_DISABLE_M17, RCD_DISABLE_FM, RCD_PAGE, RCD_CW diff --git a/Sync.cpp b/Sync.cpp index 75156af..7144683 100644 --- a/Sync.cpp +++ b/Sync.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2018,2020 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 @@ -23,6 +23,7 @@ #include "YSFDefines.h" #include "P25Defines.h" #include "NXDNDefines.h" +#include "M17Defines.h" #include #include @@ -83,3 +84,10 @@ void CSync::addNXDNSync(unsigned char* data) for (unsigned int i = 0U; i < NXDN_FSW_BYTES_LENGTH; i++) data[i] = (data[i] & ~NXDN_FSW_BYTES_MASK[i]) | NXDN_FSW_BYTES[i]; } + +void CSync::addM17Sync(unsigned char* data) +{ + assert(data != NULL); + + ::memcpy(data, M17_SYNC_BYTES, M17_SYNC_BYTES_LENGTH); +} diff --git a/Sync.h b/Sync.h index 3ff325c..2118c96 100644 --- a/Sync.h +++ b/Sync.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2018,2020 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 @@ -33,6 +33,8 @@ public: static void addNXDNSync(unsigned char* data); + static void addM17Sync(unsigned char* data); + private: }; diff --git a/Version.h b/Version.h index d8d0145..dcda43b 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20201013"; +const char* VERSION = "20201014"; #endif