From 135fd04e0dd16896c6f4691aaf1e5ea30b54c4c4 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Tue, 15 Dec 2020 15:52:27 +0000 Subject: [PATCH] Revert "Merge branch 'M17_AX25_FM'" This reverts commit e1427e3e37940b651ba27b8df5fc3b899436da37, reversing changes made to bcdba292eba8c9ec789f3dd6ea71e62a2f69cb80. --- AX25Control.cpp | 252 -- AX25Control.h | 50 - AX25Defines.h | 39 - AX25Network.cpp | 183 -- AX25Network.h | 62 - CASTInfo.cpp | 12 +- CASTInfo.h | 7 +- Conf.cpp | 1705 ++++------ Conf.h | 96 +- DMRControl.cpp | 2 +- DMRControl.h | 4 +- DMRSlot.cpp | 4 +- DMRSlot.h | 4 +- DStarControl.cpp | 6 +- Defines.h | 5 +- Display.cpp | 83 +- Display.h | 16 +- FMControl.cpp | 179 - FMControl.h | 55 - FMNetwork.cpp | 289 -- FMNetwork.h | 71 - I2CController.cpp | 69 +- I2CController.h | 18 +- IIRDirectForm1Filter.cpp | 60 - IIRDirectForm1Filter.h | 50 - ISSUES.txt | 9 + Images/M17.bmp | Bin 58014 -> 0 bytes LCDproc.cpp | 67 - LCDproc.h | 4 - M17CRC.cpp | 85 - M17CRC.h | 34 - M17Control.cpp | 802 ----- M17Control.h | 104 - M17Convolution.cpp | 275 -- M17Convolution.h | 50 - M17Defines.h | 58 - M17LICH.cpp | 143 - M17LICH.h | 57 - M17Network.cpp | 210 -- M17Network.h | 64 - M17Utils.cpp | 209 -- M17Utils.h | 41 - MMDVM.ini | 56 +- MMDVMHost.cpp | 739 +---- MMDVMHost.h | 25 +- MMDVMHost.vcxproj | 34 +- MMDVMHost.vcxproj.filters | 102 +- Makefile | 48 +- Makefile.Pi | 45 +- Makefile.Pi.Adafruit | 40 +- Makefile.Pi.HD44780 | 40 +- Makefile.Pi.OLED | 44 +- Makefile.Pi.PCF8574 | 40 +- Modem.cpp | 2147 +++++++++++- Modem.h | 260 +- ModemSerialPort.cpp | 14 +- ModemSerialPort.h | 10 +- NXDNControl.cpp | 2 +- Nextion.cpp | 75 - Nextion.h | 5 - Nextion_G4KLX/NX3224K024.HMI | Bin 593215 -> 544682 bytes Nextion_G4KLX/NX3224K024.tft | Bin 687714 -> 645650 bytes Nextion_G4KLX/NX3224K028.HMI | Bin 593215 -> 544682 bytes Nextion_G4KLX/NX3224K028.tft | Bin 687730 -> 645650 bytes Nextion_G4KLX/NX3224T024.HMI | Bin 593215 -> 544682 bytes Nextion_G4KLX/NX3224T024.tft | Bin 627024 -> 584944 bytes Nextion_G4KLX/NX3224T028.HMI | Bin 593215 -> 544682 bytes Nextion_G4KLX/NX3224T028.tft | Bin 627024 -> 584944 bytes Nextion_G4KLX/NX4024K032.HMI | Bin 614446 -> 565913 bytes Nextion_G4KLX/NX4024K032.tft | Bin 708495 -> 662308 bytes Nextion_G4KLX/NX4024T032.HMI | Bin 614446 -> 565913 bytes Nextion_G4KLX/NX4024T032.tft | Bin 647789 -> 601602 bytes Nextion_G4KLX/NX4832K035.HMI | Bin 1159304 -> 1110771 bytes Nextion_G4KLX/NX4832K035.tft | Bin 1217059 -> 100698 bytes Nextion_G4KLX/NX4832T035.HMI | Bin 1159304 -> 1110771 bytes Nextion_G4KLX/NX4832T035.tft | Bin 1156321 -> 1114218 bytes NullDisplay.cpp | 14 - NullDisplay.h | 3 - NullModem.cpp | 10 +- NullModem.h | 128 +- OLED.cpp | 57 +- OLED.h | 6 - PseudoTTYController.cpp | 86 - PseudoTTYController.h | 43 - README.md | 10 +- RemoteControl.cpp | 18 +- RemoteControl.h | 7 +- SerialController.cpp | 185 +- SerialController.h | 21 +- SerialModem.cpp | 2869 ----------------- SerialModem.h | 286 -- Sync.cpp | 17 +- Sync.h | 5 +- TFTSerial.cpp | 588 ++++ TFTSerial.h | 89 + TFTSerial/DMR_sm.bmp | Bin 0 -> 24054 bytes TFTSerial/DStar_sm.bmp | Bin 0 -> 24054 bytes TFTSerial/MMDVM_sm.bmp | Bin 0 -> 25494 bytes TFTSerial/NXDN_sm.bmp | Bin 0 -> 23574 bytes TFTSerial/P25_sm.bmp | Bin 0 -> 24054 bytes TFTSerial/YSF_sm.bmp | Bin 0 -> 24054 bytes TFTSurenoo.cpp | 24 - TFTSurenoo.h | 3 - Tools/DeEmphasis.py | 43 - Tools/PreEmphasis.py | 51 - UMP.cpp | 283 ++ UMP.h | 63 + UMP/UMP.ino | 203 ++ linux/{pi-star => }/init/README.md | 0 linux/{pi-star => }/init/mmdvmhost | 0 linux/pi-star/systemd/mmdvmhost.service | 12 - linux/{pi-star => }/systemd/README.md | 0 linux/systemd/mmdvmhost.service | 8 +- linux/{pi-star => }/systemd/mmdvmhost.timer | 0 linux/{pi-star => }/systemd/mmdvmhost_service | 0 115 files changed, 4808 insertions(+), 9583 deletions(-) delete mode 100644 AX25Control.cpp delete mode 100644 AX25Control.h delete mode 100644 AX25Defines.h delete mode 100644 AX25Network.cpp delete mode 100644 AX25Network.h delete mode 100644 FMControl.cpp delete mode 100644 FMControl.h delete mode 100644 FMNetwork.cpp delete mode 100644 FMNetwork.h delete mode 100644 IIRDirectForm1Filter.cpp delete mode 100644 IIRDirectForm1Filter.h create mode 100644 ISSUES.txt delete mode 100644 Images/M17.bmp delete mode 100644 M17CRC.cpp delete mode 100644 M17CRC.h delete mode 100644 M17Control.cpp delete mode 100644 M17Control.h delete mode 100644 M17Convolution.cpp delete mode 100644 M17Convolution.h delete mode 100644 M17Defines.h delete mode 100644 M17LICH.cpp delete mode 100644 M17LICH.h delete mode 100644 M17Network.cpp delete mode 100644 M17Network.h delete mode 100644 M17Utils.cpp delete mode 100644 M17Utils.h delete mode 100644 PseudoTTYController.cpp delete mode 100644 PseudoTTYController.h delete mode 100644 SerialModem.cpp delete mode 100644 SerialModem.h create mode 100644 TFTSerial.cpp create mode 100644 TFTSerial.h create mode 100644 TFTSerial/DMR_sm.bmp create mode 100644 TFTSerial/DStar_sm.bmp create mode 100644 TFTSerial/MMDVM_sm.bmp create mode 100644 TFTSerial/NXDN_sm.bmp create mode 100644 TFTSerial/P25_sm.bmp create mode 100644 TFTSerial/YSF_sm.bmp delete mode 100644 Tools/DeEmphasis.py delete mode 100644 Tools/PreEmphasis.py create mode 100644 UMP.cpp create mode 100644 UMP.h create mode 100644 UMP/UMP.ino rename linux/{pi-star => }/init/README.md (100%) rename linux/{pi-star => }/init/mmdvmhost (100%) delete mode 100644 linux/pi-star/systemd/mmdvmhost.service rename linux/{pi-star => }/systemd/README.md (100%) rename linux/{pi-star => }/systemd/mmdvmhost.timer (100%) rename linux/{pi-star => }/systemd/mmdvmhost_service (100%) diff --git a/AX25Control.cpp b/AX25Control.cpp deleted file mode 100644 index 3b108fc..0000000 --- a/AX25Control.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 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 "AX25Control.h" -#include "AX25Defines.h" -#include "Utils.h" -#include "Log.h" - -#include -#include -#include -#include - -// #define DUMP_AX25 - -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]) - -CAX25Control::CAX25Control(CAX25Network* network, bool trace) : -m_network(network), -m_trace(trace), -m_enabled(true), -m_fp(NULL) -{ -} - -CAX25Control::~CAX25Control() -{ -} - -bool CAX25Control::writeModem(unsigned char *data, unsigned int len) -{ - assert(data != NULL); - - if (!m_enabled) - return false; - - if (m_trace) - decode(data, len); - - CUtils::dump(1U, "AX.25 received packet", data, len); - - if (m_network == NULL) - return true; - - return m_network->write(data, len); -} - -unsigned int CAX25Control::readModem(unsigned char* data) -{ - assert(data != NULL); - - if (m_network == NULL) - return 0U; - - if (!m_enabled) - return 0U; - - unsigned int length = m_network->read(data, 500U); - - if (length > 0U) - CUtils::dump(1U, "AX.25 transmitted packet", data, length); - - return length; -} - -bool CAX25Control::openFile() -{ - if (m_fp != NULL) - return true; - - time_t t; - ::time(&t); - - struct tm* tm = ::localtime(&t); - - char name[100U]; - ::sprintf(name, "AX25_%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("AX25", 1U, 4U, m_fp); - - return true; -} - -bool CAX25Control::writeFile(const unsigned char* data, unsigned int length) -{ - if (m_fp == NULL) - return false; - - ::fwrite(&length, 1U, sizeof(unsigned int), m_fp); - ::fwrite(data, 1U, length, m_fp); - - return true; -} - -void CAX25Control::closeFile() -{ - if (m_fp != NULL) { - ::fclose(m_fp); - m_fp = NULL; - } -} - -void CAX25Control::enable(bool enabled) -{ - m_enabled = enabled; -} - -void CAX25Control::decode(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length >= 15U); - - std::string text; - - bool more = decodeAddress(data + 7U, text); - - text += '>'; - - decodeAddress(data + 0U, text); - - unsigned int n = 14U; - while (more && n < length) { - text += ','; - more = decodeAddress(data + n, text, true); - n += 7U; - } - - text += ' '; - - if ((data[n] & 0x01U) == 0x00U) { - // I frame - char t[20U]; - ::sprintf(t, "", (data[n] >> 1) & 0x07U, (data[n] >> 5) & 0x07U); - text += t; - } else { - if ((data[n] & 0x02U) == 0x00U) { - // S frame - char t[20U]; - switch (data[n] & 0x0FU) { - case 0x01U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x05U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x09U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x0DU: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - default: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - } - - text += t; - LogMessage("AX.25, %s", text.c_str()); - return; - } else { - // U frame - switch (data[n] & 0xEFU) { - case 0x6FU: - text += ""; - break; - case 0x2FU: - text += ""; - break; - case 0x43U: - text += ""; - break; - case 0x0FU: - text += ""; - break; - case 0x63U: - text += ""; - break; - case 0x87U: - text += ""; - break; - case 0x03U: - text += ""; - break; - case 0xAFU: - text += ""; - break; - case 0xE3U: - text += ""; - break; - default: - text += ""; - break; - } - - if ((data[n] & 0xEFU) != 0x03U) { - LogMessage("AX.25, %s", text.c_str()); - return; - } - } - } - - n += 2U; - - LogMessage("AX.25, %s %.*s", text.c_str(), length - n, data + n); -} - -bool CAX25Control::decodeAddress(const unsigned char* data, std::string& text, bool isDigi) const -{ - assert(data != NULL); - - for (unsigned int i = 0U; i < 6U; i++) { - char c = data[i] >> 1; - if (c != ' ') - text += c; - } - - unsigned char ssid = (data[6U] >> 1) & 0x0FU; - if (ssid > 0U) { - text += '-'; - if (ssid >= 10U) { - text += '1'; - text += '0' + ssid - 10U; - } - else { - text += '0' + ssid; - } - } - - if (isDigi) { - if ((data[6U] & 0x80U) == 0x80U) - text += '*'; - } - - return (data[6U] & 0x01U) == 0x00U; -} diff --git a/AX25Control.h b/AX25Control.h deleted file mode 100644 index b4fed2a..0000000 --- a/AX25Control.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 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(AX25Control_H) -#define AX25Control_H - -#include "AX25Network.h" - -#include - -class CAX25Control { -public: - CAX25Control(CAX25Network* network, bool trace); - ~CAX25Control(); - - bool writeModem(unsigned char* data, unsigned int len); - - unsigned int readModem(unsigned char* data); - - void enable(bool enabled); - -private: - CAX25Network* m_network; - bool m_trace; - bool m_enabled; - FILE* m_fp; - - void decode(const unsigned char* data, unsigned int length); - bool decodeAddress(const unsigned char* data, std::string& text, bool isDigi = false) const; - bool openFile(); - bool writeFile(const unsigned char* data, unsigned int length); - void closeFile(); -}; - -#endif diff --git a/AX25Defines.h b/AX25Defines.h deleted file mode 100644 index a0f4c80..0000000 --- a/AX25Defines.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 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(AX25Defines_H) -#define AX25Defines_H - -const unsigned int AX25_CALLSIGN_TEXT_LENGTH = 6U; -const unsigned int AX25_SSID_LENGTH = 1U; -const unsigned int AX25_CALLSIGN_LENGTH = 7U; - -const unsigned int AX25_MAX_DIGIPEATERS = 6U; - -const unsigned char AX25_PID_NOL3 = 0xF0U; - -const unsigned int AX25_MAX_FRAME_LENGTH_BYTES = 330U; // Callsign (7) + Callsign (7) + 8 Digipeaters (56) + - // Control (1) + PID (1) + Data (256) + Checksum (2) -const unsigned char AX25_KISS_DATA = 0x00U; - -const unsigned char AX25_FEND = 0xC0U; -const unsigned char AX25_FESC = 0xDBU; -const unsigned char AX25_TFEND = 0xDCU; -const unsigned char AX25_TFESC = 0xDDU; - -#endif diff --git a/AX25Network.cpp b/AX25Network.cpp deleted file mode 100644 index d7f876d..0000000 --- a/AX25Network.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 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 "AX25Network.h" -#include "AX25Defines.h" -#include "Defines.h" -#include "Utils.h" -#include "Log.h" - -#include -#include -#include - -const unsigned int BUFFER_LENGTH = 500U; - - -CAX25Network::CAX25Network(const std::string& port, unsigned int speed, bool debug) : -m_serial(port, speed, false), -m_txData(NULL), -m_rxData(NULL), -m_rxLength(0U), -m_rxLastChar(0U), -m_debug(debug), -m_enabled(false) -{ - assert(!port.empty()); - assert(speed > 0U); - - m_txData = new unsigned char[BUFFER_LENGTH]; - m_rxData = new unsigned char[BUFFER_LENGTH]; -} - -CAX25Network::~CAX25Network() -{ - delete[] m_txData; - delete[] m_rxData; -} - -bool CAX25Network::open() -{ - LogMessage("Opening AX25 network connection"); - - return m_serial.open(); -} - -bool CAX25Network::write(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - - if (!m_enabled) - return true; - - unsigned int txLength = 0U; - - m_txData[txLength++] = AX25_FEND; - m_txData[txLength++] = AX25_KISS_DATA; - - for (unsigned int i = 0U; i < length; i++) { - unsigned char c = data[i]; - - switch (c) { - case AX25_FEND: - m_txData[txLength++] = AX25_FESC; - m_txData[txLength++] = AX25_TFEND; - break; - case AX25_FESC: - m_txData[txLength++] = AX25_FESC; - m_txData[txLength++] = AX25_TFESC; - break; - default: - m_txData[txLength++] = c; - break; - } - } - - m_txData[txLength++] = AX25_FEND; - - if (m_debug) - CUtils::dump(1U, "AX25 Network Data Sent", m_txData, txLength); - - return m_serial.write(m_txData, txLength); -} - -unsigned int CAX25Network::read(unsigned char* data, unsigned int length) -{ - assert(data != NULL); - - bool complete = false; - - unsigned char c; - while (m_serial.read(&c, 1U) > 0) { - if (m_rxLength == 0U && c == AX25_FEND) - m_rxData[m_rxLength++] = c; - else if (m_rxLength > 0U) - m_rxData[m_rxLength++] = c; - - if (m_rxLength > 1U && c == AX25_FEND) { - complete = true; - break; - } - } - - if (!m_enabled) - return 0U; - - if (!complete) - return 0U; - - if (m_rxLength == 0U) - return 0U; - - if (m_rxData[1U] != AX25_KISS_DATA) { - m_rxLength = 0U; - return 0U; - } - - complete = false; - - unsigned int dataLen = 0U; - for (unsigned int i = 2U; i < m_rxLength; i++) { - unsigned char c = m_rxData[i]; - - if (c == AX25_FEND) { - complete = true; - break; - } else if (c == AX25_TFEND && m_rxLastChar == AX25_FESC) { - data[dataLen++] = AX25_FEND; - } else if (c == AX25_TFESC && m_rxLastChar == AX25_FESC) { - data[dataLen++] = AX25_FESC; - } else { - data[dataLen++] = c; - } - - m_rxLastChar = c; - } - - if (!complete) - return 0U; - - if (m_debug) - CUtils::dump(1U, "AX25 Network Data Received", m_rxData, m_rxLength); - - m_rxLength = 0U; - m_rxLastChar = 0U; - - return dataLen; -} - -void CAX25Network::reset() -{ -} - -void CAX25Network::close() -{ - m_serial.close(); - - LogMessage("Closing AX25 network connection"); -} - -void CAX25Network::enable(bool enabled) -{ - m_enabled = enabled; - - if (enabled != m_enabled) { - m_rxLastChar = 0U; - m_rxLength = 0U; - } -} diff --git a/AX25Network.h b/AX25Network.h deleted file mode 100644 index f46de15..0000000 --- a/AX25Network.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 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 AX25Network_H -#define AX25Network_H - -#if defined(_WIN32) || defined(_WIN64) -#include "SerialController.h" -#else -#include "PseudoTTYController.h" -#endif - -#include -#include - -class CAX25Network { -public: - CAX25Network(const std::string& port, unsigned int speed, bool debug); - ~CAX25Network(); - - bool open(); - - void enable(bool enabled); - - bool write(const unsigned char* data, unsigned int length); - - unsigned int read(unsigned char* data, unsigned int length); - - void reset(); - - void close(); - -private: -#if defined(_WIN32) || defined(_WIN64) - CSerialController m_serial; -#else - CPseudoTTYController m_serial; -#endif - unsigned char* m_txData; - unsigned char* m_rxData; - unsigned int m_rxLength; - unsigned char m_rxLastChar; - bool m_debug; - bool m_enabled; -}; - -#endif diff --git a/CASTInfo.cpp b/CASTInfo.cpp index 4078d2d..603ee20 100644 --- a/CASTInfo.cpp +++ b/CASTInfo.cpp @@ -21,7 +21,7 @@ static bool networkInfoInitialized = false; static unsigned char passCounter = 0; -CCASTInfo::CCASTInfo(IModem* modem) : +CCASTInfo::CCASTInfo(CModem* modem) : CDisplay(), m_modem(modem), m_ipaddress() @@ -130,16 +130,6 @@ void CCASTInfo::clearNXDNInt() { } -void CCASTInfo::writeM17Int(const char* source, const char* dest, const char* type) -{ - if (m_modem != NULL) - m_modem->writeM17Info(source, dest, type); -} - -void CCASTInfo::clearM17Int() -{ -} - void CCASTInfo::writePOCSAGInt(uint32_t ric, const std::string& message) { if (m_modem != NULL) diff --git a/CASTInfo.h b/CASTInfo.h index 38092b7..8945dcd 100644 --- a/CASTInfo.h +++ b/CASTInfo.h @@ -28,7 +28,7 @@ class CCASTInfo : public CDisplay { public: - CCASTInfo(IModem* modem); + CCASTInfo(CModem* modem); virtual ~CCASTInfo(); virtual bool open(); @@ -57,9 +57,6 @@ protected: virtual void writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type); virtual void clearNXDNInt(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void clearM17Int(); - virtual void writePOCSAGInt(uint32_t ric, const std::string& message); virtual void clearPOCSAGInt(); @@ -67,7 +64,7 @@ protected: virtual void clearCWInt(); private: - IModem* m_modem; + CModem* m_modem; std::string m_ipaddress; }; diff --git a/Conf.cpp b/Conf.cpp index 4dbafbf..bc2d6a5 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -28,40 +28,36 @@ const int BUFFER_SIZE = 500; enum SECTION { - SECTION_NONE, - SECTION_GENERAL, - SECTION_INFO, - SECTION_LOG, - SECTION_CWID, - SECTION_DMRID_LOOKUP, - SECTION_NXDNID_LOOKUP, - SECTION_MODEM, - SECTION_TRANSPARENT, - SECTION_DSTAR, - SECTION_DMR, - SECTION_FUSION, - SECTION_P25, - SECTION_NXDN, - SECTION_M17, - SECTION_POCSAG, - SECTION_FM, - SECTION_AX25, - SECTION_DSTAR_NETWORK, - SECTION_DMR_NETWORK, - SECTION_FUSION_NETWORK, - SECTION_P25_NETWORK, - SECTION_NXDN_NETWORK, - SECTION_M17_NETWORK, - SECTION_POCSAG_NETWORK, - SECTION_FM_NETWORK, - SECTION_AX25_NETWORK, - SECTION_TFTSERIAL, - SECTION_HD44780, - SECTION_NEXTION, - SECTION_OLED, - SECTION_LCDPROC, - SECTION_LOCK_FILE, - SECTION_REMOTE_CONTROL + SECTION_NONE, + SECTION_GENERAL, + SECTION_INFO, + SECTION_LOG, + SECTION_CWID, + SECTION_DMRID_LOOKUP, + SECTION_NXDNID_LOOKUP, + SECTION_MODEM, + SECTION_TRANSPARENT, + SECTION_UMP, + SECTION_DSTAR, + SECTION_DMR, + SECTION_FUSION, + SECTION_P25, + SECTION_NXDN, + SECTION_POCSAG, + SECTION_FM, + SECTION_DSTAR_NETWORK, + SECTION_DMR_NETWORK, + SECTION_FUSION_NETWORK, + SECTION_P25_NETWORK, + SECTION_NXDN_NETWORK, + SECTION_POCSAG_NETWORK, + SECTION_TFTSERIAL, + SECTION_HD44780, + SECTION_NEXTION, + SECTION_OLED, + SECTION_LCDPROC, + SECTION_LOCK_FILE, + SECTION_REMOTE_CONTROL }; CConf::CConf(const std::string& file) : @@ -95,7 +91,6 @@ m_nxdnIdLookupFile(), m_nxdnIdLookupTime(0U), m_modemPort(), m_modemProtocol("uart"), -m_modemSpeed(115200U), m_modemAddress(0x22), m_modemRXInvert(false), m_modemTXInvert(false), @@ -114,10 +109,8 @@ 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_modemAX25TXLevel(50.0F), m_modemRSSIMappingFile(), m_modemUseCOSAsLockout(false), m_modemTrace(false), @@ -127,6 +120,8 @@ m_transparentRemoteAddress(), m_transparentRemotePort(0U), m_transparentLocalPort(0U), m_transparentSendFrameType(0U), +m_umpEnabled(false), +m_umpPort(), m_dstarEnabled(false), m_dstarModule("C"), m_dstarSelfOnly(false), @@ -177,12 +172,6 @@ m_nxdnSelfOnly(false), m_nxdnRemoteGateway(false), m_nxdnTXHang(5U), m_nxdnModeHang(10U), -m_m17Enabled(false), -m_m17ColorCode(1U), -m_m17SelfOnly(false), -m_m17AllowEncryption(false), -m_m17TXHang(5U), -m_m17ModeHang(10U), m_pocsagEnabled(false), m_pocsagFrequency(0U), m_fmEnabled(false), @@ -213,19 +202,9 @@ m_fmKerchunkTime(0U), m_fmHangTime(7U), m_fmAccessMode(1U), m_fmCOSInvert(false), -m_fmNoiseSquelch(false), -m_fmSquelchHighThreshold(30U), -m_fmSquelchLowThreshold(20U), m_fmRFAudioBoost(1U), m_fmMaxDevLevel(90.0F), m_fmExtAudioBoost(1U), -m_fmModeHang(10U), -m_ax25Enabled(false), -m_ax25TXDelay(300U), -m_ax25RXTwist(6), -m_ax25SlotTime(30U), -m_ax25PPersist(128U), -m_ax25Trace(false), m_dstarNetworkEnabled(false), m_dstarGatewayAddress(), m_dstarGatewayPort(0U), @@ -265,12 +244,6 @@ 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), @@ -278,18 +251,6 @@ m_pocsagLocalAddress(), m_pocsagLocalPort(0U), m_pocsagNetworkModeHang(3U), m_pocsagNetworkDebug(false), -m_fmNetworkEnabled(false), -m_fmGatewayAddress(), -m_fmGatewayPort(0U), -m_fmLocalAddress(), -m_fmLocalPort(0U), -m_fmSampleRate(8000U), -m_fmNetworkModeHang(3U), -m_fmNetworkDebug(false), -m_ax25NetworkEnabled(false), -m_ax25NetworkPort(), -m_ax25NetworkSpeed(9600U), -m_ax25NetworkDebug(false), m_tftSerialPort("/dev/ttyAMA0"), m_tftSerialBrightness(50U), m_hd44780Rows(2U), @@ -335,368 +296,369 @@ CConf::~CConf() bool CConf::read() { - FILE* fp = ::fopen(m_file.c_str(), "rt"); - if (fp == NULL) { - ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); - return false; - } + FILE* fp = ::fopen(m_file.c_str(), "rt"); + if (fp == NULL) { + ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); + return false; + } - SECTION section = SECTION_NONE; + SECTION section = SECTION_NONE; - char buffer[BUFFER_SIZE]; - while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) { - if (buffer[0U] == '#') - continue; + char buffer[BUFFER_SIZE]; + while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) { + if (buffer[0U] == '#') + continue; - if (buffer[0U] == '[') { - if (::strncmp(buffer, "[General]", 9U) == 0) - section = SECTION_GENERAL; - else if (::strncmp(buffer, "[Info]", 6U) == 0) - section = SECTION_INFO; - else if (::strncmp(buffer, "[Log]", 5U) == 0) - section = SECTION_LOG; - else if (::strncmp(buffer, "[CW Id]", 7U) == 0) - section = SECTION_CWID; - else if (::strncmp(buffer, "[DMR Id Lookup]", 15U) == 0) - section = SECTION_DMRID_LOOKUP; - else if (::strncmp(buffer, "[NXDN Id Lookup]", 16U) == 0) - section = SECTION_NXDNID_LOOKUP; - else if (::strncmp(buffer, "[Modem]", 7U) == 0) - section = SECTION_MODEM; - else if (::strncmp(buffer, "[Transparent Data]", 18U) == 0) - section = SECTION_TRANSPARENT; - else if (::strncmp(buffer, "[D-Star]", 8U) == 0) - section = SECTION_DSTAR; - else if (::strncmp(buffer, "[DMR]", 5U) == 0) - section = SECTION_DMR; - else if (::strncmp(buffer, "[System Fusion]", 15U) == 0) - 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, "[M17]", 5U) == 0) - section = SECTION_M17; - else if (::strncmp(buffer, "[POCSAG]", 8U) == 0) - section = SECTION_POCSAG; - else if (::strncmp(buffer, "[FM]", 4U) == 0) - section = SECTION_FM; - else if (::strncmp(buffer, "[AX.25]", 7U) == 0) - section = SECTION_AX25; - else if (::strncmp(buffer, "[D-Star Network]", 16U) == 0) - section = SECTION_DSTAR_NETWORK; - else if (::strncmp(buffer, "[DMR Network]", 13U) == 0) - section = SECTION_DMR_NETWORK; - else if (::strncmp(buffer, "[System Fusion Network]", 23U) == 0) - 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, "[M17 Network]", 13U) == 0) - section = SECTION_M17_NETWORK; - else if (::strncmp(buffer, "[POCSAG Network]", 16U) == 0) - section = SECTION_POCSAG_NETWORK; - else if (::strncmp(buffer, "[FM Network]", 12U) == 0) - section = SECTION_FM_NETWORK; - else if (::strncmp(buffer, "[AX.25 Network]", 15U) == 0) - section = SECTION_AX25_NETWORK; - else if (::strncmp(buffer, "[TFT Serial]", 12U) == 0) - section = SECTION_TFTSERIAL; - else if (::strncmp(buffer, "[HD44780]", 9U) == 0) - section = SECTION_HD44780; - else if (::strncmp(buffer, "[Nextion]", 9U) == 0) - section = SECTION_NEXTION; - else if (::strncmp(buffer, "[OLED]", 6U) == 0) - section = SECTION_OLED; - else if (::strncmp(buffer, "[LCDproc]", 9U) == 0) - section = SECTION_LCDPROC; - else if (::strncmp(buffer, "[Lock File]", 11U) == 0) - section = SECTION_LOCK_FILE; - else if (::strncmp(buffer, "[Remote Control]", 16U) == 0) - section = SECTION_REMOTE_CONTROL; - else - section = SECTION_NONE; + if (buffer[0U] == '[') { + if (::strncmp(buffer, "[General]", 9U) == 0) + section = SECTION_GENERAL; + else if (::strncmp(buffer, "[Info]", 6U) == 0) + section = SECTION_INFO; + else if (::strncmp(buffer, "[Log]", 5U) == 0) + section = SECTION_LOG; + else if (::strncmp(buffer, "[CW Id]", 7U) == 0) + section = SECTION_CWID; + else if (::strncmp(buffer, "[DMR Id Lookup]", 15U) == 0) + section = SECTION_DMRID_LOOKUP; + else if (::strncmp(buffer, "[NXDN Id Lookup]", 16U) == 0) + section = SECTION_NXDNID_LOOKUP; + else if (::strncmp(buffer, "[Modem]", 7U) == 0) + section = SECTION_MODEM; + else if (::strncmp(buffer, "[Transparent Data]", 18U) == 0) + section = SECTION_TRANSPARENT; + else if (::strncmp(buffer, "[UMP]", 5U) == 0) + section = SECTION_UMP; + else if (::strncmp(buffer, "[D-Star]", 8U) == 0) + section = SECTION_DSTAR; + else if (::strncmp(buffer, "[DMR]", 5U) == 0) + section = SECTION_DMR; + else if (::strncmp(buffer, "[System Fusion]", 15U) == 0) + 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, "[POCSAG]", 8U) == 0) + section = SECTION_POCSAG; + else if (::strncmp(buffer, "[FM]", 4U) == 0) + section = SECTION_FM; + else if (::strncmp(buffer, "[D-Star Network]", 16U) == 0) + section = SECTION_DSTAR_NETWORK; + else if (::strncmp(buffer, "[DMR Network]", 13U) == 0) + section = SECTION_DMR_NETWORK; + else if (::strncmp(buffer, "[System Fusion Network]", 23U) == 0) + 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, "[POCSAG Network]", 16U) == 0) + section = SECTION_POCSAG_NETWORK; + else if (::strncmp(buffer, "[TFT Serial]", 12U) == 0) + section = SECTION_TFTSERIAL; + else if (::strncmp(buffer, "[HD44780]", 9U) == 0) + section = SECTION_HD44780; + else if (::strncmp(buffer, "[Nextion]", 9U) == 0) + section = SECTION_NEXTION; + else if (::strncmp(buffer, "[OLED]", 6U) == 0) + section = SECTION_OLED; + else if (::strncmp(buffer, "[LCDproc]", 9U) == 0) + section = SECTION_LCDPROC; + else if (::strncmp(buffer, "[Lock File]", 11U) == 0) + section = SECTION_LOCK_FILE; + else if (::strncmp(buffer, "[Remote Control]", 16U) == 0) + section = SECTION_REMOTE_CONTROL; + else + section = SECTION_NONE; - continue; + continue; + } + + char* key = ::strtok(buffer, " \t=\r\n"); + if (key == NULL) + continue; + + char* value = ::strtok(NULL, "\r\n"); + if (value == NULL) + continue; + + // Remove quotes from the value + size_t len = ::strlen(value); + if (len > 1U && *value == '"' && value[len - 1U] == '"') { + value[len - 1U] = '\0'; + value++; + } else { + char *p; + + // if value is not quoted, remove after # (to make comment) + if ((p = strchr(value, '#')) != NULL) + *p = '\0'; + + // remove trailing tab/space + for (p = value + strlen(value) - 1; + p >= value && (*p == '\t' || *p == ' '); p--) + *p = '\0'; + } + + if (section == SECTION_GENERAL) { + if (::strcmp(key, "Callsign") == 0) { + // Convert the callsign to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_fmCallsign = m_cwIdCallsign = m_callsign = value; + } else if (::strcmp(key, "Id") == 0) + m_id = m_p25Id = m_dmrId = (unsigned int)::atoi(value); + else if (::strcmp(key, "Timeout") == 0) + m_fmTimeout = m_timeout = (unsigned int)::atoi(value); + 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); + else if (::strcmp(key, "RFModeHang") == 0) + m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "NetModeHang") == 0) + m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Display") == 0) + m_display = value; + else if (::strcmp(key, "Daemon") == 0) + m_daemon = ::atoi(value) == 1; + } else if (section == SECTION_INFO) { + if (::strcmp(key, "TXFrequency") == 0) + m_pocsagFrequency = m_txFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "RXFrequency") == 0) + m_rxFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "Power") == 0) + m_power = (unsigned int)::atoi(value); + else if (::strcmp(key, "Latitude") == 0) + m_latitude = float(::atof(value)); + else if (::strcmp(key, "Longitude") == 0) + m_longitude = float(::atof(value)); + else if (::strcmp(key, "Height") == 0) + m_height = ::atoi(value); + else if (::strcmp(key, "Location") == 0) + m_location = value; + else if (::strcmp(key, "Description") == 0) + m_description = value; + else if (::strcmp(key, "URL") == 0) + m_url = value; + } else if (section == SECTION_LOG) { + if (::strcmp(key, "FilePath") == 0) + m_logFilePath = value; + else if (::strcmp(key, "FileRoot") == 0) + m_logFileRoot = value; + else if (::strcmp(key, "FileLevel") == 0) + m_logFileLevel = (unsigned int)::atoi(value); + else if (::strcmp(key, "DisplayLevel") == 0) + m_logDisplayLevel = (unsigned int)::atoi(value); + else if (::strcmp(key, "FileRotate") == 0) + m_logFileRotate = ::atoi(value) == 1; + } else if (section == SECTION_CWID) { + if (::strcmp(key, "Enable") == 0) + m_cwIdEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Time") == 0) + m_cwIdTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "Callsign") == 0) { + // Convert the callsign to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_cwIdCallsign = value; } - - char* key = ::strtok(buffer, " \t=\r\n"); - if (key == NULL) - continue; - - char* value = ::strtok(NULL, "\r\n"); - if (value == NULL) - continue; - - // Remove quotes from the value - size_t len = ::strlen(value); - if (len > 1U && *value == '"' && value[len - 1U] == '"') { - value[len - 1U] = '\0'; - value++; - } else { - // if value is not quoted, remove after # (to make comment) - ::strtok(value, "#"); - } - - if (section == SECTION_GENERAL) { - if (::strcmp(key, "Callsign") == 0) { - // Convert the callsign to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_fmCallsign = m_cwIdCallsign = m_callsign = value; - } else if (::strcmp(key, "Id") == 0) - m_id = m_p25Id = m_dmrId = (unsigned int)::atoi(value); - else if (::strcmp(key, "Timeout") == 0) - m_fmTimeout = m_timeout = (unsigned int)::atoi(value); - 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_nxdnNetworkModeHang = m_m17NetworkModeHang = m_fmNetworkModeHang = - m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = m_nxdnModeHang = m_m17ModeHang = m_fmModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "RFModeHang") == 0) - m_dstarModeHang = m_dmrModeHang = m_fusionModeHang = m_p25ModeHang = m_nxdnModeHang = m_m17ModeHang = m_fmModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "NetModeHang") == 0) - m_dstarNetworkModeHang = m_dmrNetworkModeHang = m_fusionNetworkModeHang = m_p25NetworkModeHang = m_nxdnNetworkModeHang = m_m17NetworkModeHang = m_fmNetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Display") == 0) - m_display = value; - else if (::strcmp(key, "Daemon") == 0) - m_daemon = ::atoi(value) == 1; - } else if (section == SECTION_INFO) { - if (::strcmp(key, "TXFrequency") == 0) - m_pocsagFrequency = m_txFrequency = (unsigned int)::atoi(value); - else if (::strcmp(key, "RXFrequency") == 0) - m_rxFrequency = (unsigned int)::atoi(value); - else if (::strcmp(key, "Power") == 0) - m_power = (unsigned int)::atoi(value); - else if (::strcmp(key, "Latitude") == 0) - m_latitude = float(::atof(value)); - else if (::strcmp(key, "Longitude") == 0) - m_longitude = float(::atof(value)); - else if (::strcmp(key, "Height") == 0) - m_height = ::atoi(value); - else if (::strcmp(key, "Location") == 0) - m_location = value; - else if (::strcmp(key, "Description") == 0) - m_description = value; - else if (::strcmp(key, "URL") == 0) - m_url = value; - } else if (section == SECTION_LOG) { - if (::strcmp(key, "FilePath") == 0) - m_logFilePath = value; - else if (::strcmp(key, "FileRoot") == 0) - m_logFileRoot = value; - else if (::strcmp(key, "FileLevel") == 0) - m_logFileLevel = (unsigned int)::atoi(value); - else if (::strcmp(key, "DisplayLevel") == 0) - m_logDisplayLevel = (unsigned int)::atoi(value); - else if (::strcmp(key, "FileRotate") == 0) - m_logFileRotate = ::atoi(value) == 1; - } else if (section == SECTION_CWID) { - if (::strcmp(key, "Enable") == 0) - m_cwIdEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Time") == 0) - m_cwIdTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "Callsign") == 0) { - // Convert the callsign to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_cwIdCallsign = value; + } else if (section == SECTION_DMRID_LOOKUP) { + if (::strcmp(key, "File") == 0) + m_dmrIdLookupFile = value; + else if (::strcmp(key, "Time") == 0) + m_dmrIdLookupTime = (unsigned int)::atoi(value); + } else if (section == SECTION_NXDNID_LOOKUP) { + if (::strcmp(key, "File") == 0) + m_nxdnIdLookupFile = value; + else if (::strcmp(key, "Time") == 0) + m_nxdnIdLookupTime = (unsigned int)::atoi(value); + } else if (section == SECTION_MODEM) { + if (::strcmp(key, "Port") == 0) + m_modemPort = value; + else if (::strcmp(key, "Protocol") == 0) + m_modemProtocol = value; + else if (::strcmp(key, "Address") == 0) + m_modemAddress = (unsigned int)::strtoul(value, NULL, 16); + else if (::strcmp(key, "RXInvert") == 0) + m_modemRXInvert = ::atoi(value) == 1; + else if (::strcmp(key, "TXInvert") == 0) + m_modemTXInvert = ::atoi(value) == 1; + else if (::strcmp(key, "PTTInvert") == 0) + m_modemPTTInvert = ::atoi(value) == 1; + else if (::strcmp(key, "TXDelay") == 0) + m_modemTXDelay = (unsigned int)::atoi(value); + else if (::strcmp(key, "DMRDelay") == 0) + m_modemDMRDelay = (unsigned int)::atoi(value); + else if (::strcmp(key, "RXOffset") == 0) + m_modemRXOffset = ::atoi(value); + else if (::strcmp(key, "TXOffset") == 0) + m_modemTXOffset = ::atoi(value); + else if (::strcmp(key, "RXDCOffset") == 0) + m_modemRXDCOffset = ::atoi(value); + else if (::strcmp(key, "TXDCOffset") == 0) + m_modemTXDCOffset = ::atoi(value); + else if (::strcmp(key, "RFLevel") == 0) + m_modemRFLevel = float(::atof(value)); + 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)); + else if (::strcmp(key, "CWIdTXLevel") == 0) + m_modemCWIdTXLevel = float(::atof(value)); + else if (::strcmp(key, "D-StarTXLevel") == 0) + m_modemDStarTXLevel = float(::atof(value)); + else if (::strcmp(key, "DMRTXLevel") == 0) + m_modemDMRTXLevel = float(::atof(value)); + else if (::strcmp(key, "YSFTXLevel") == 0) + 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, "POCSAGTXLevel") == 0) + m_modemPOCSAGTXLevel = float(::atof(value)); + else if (::strcmp(key, "FMTXLevel") == 0) + m_modemFMTXLevel = float(::atof(value)); + else if (::strcmp(key, "RSSIMappingFile") == 0) + m_modemRSSIMappingFile = value; + else if (::strcmp(key, "UseCOSAsLockout") == 0) + m_modemUseCOSAsLockout = ::atoi(value) == 1; + else if (::strcmp(key, "Trace") == 0) + m_modemTrace = ::atoi(value) == 1; + else if (::strcmp(key, "Debug") == 0) + m_modemDebug = ::atoi(value) == 1; + } else if (section == SECTION_TRANSPARENT) { + if (::strcmp(key, "Enable") == 0) + m_transparentEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "RemoteAddress") == 0) + m_transparentRemoteAddress = value; + else if (::strcmp(key, "RemotePort") == 0) + m_transparentRemotePort = (unsigned int)::atoi(value); + else if (::strcmp(key, "LocalPort") == 0) + m_transparentLocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "SendFrameType") == 0) + m_transparentSendFrameType = (unsigned int)::atoi(value); + } else if (section == SECTION_UMP) { + if (::strcmp(key, "Enable") == 0) + m_umpEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Port") == 0) + m_umpPort = value; + } else if (section == SECTION_DSTAR) { + if (::strcmp(key, "Enable") == 0) + m_dstarEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Module") == 0) { + // Convert the module to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_dstarModule = value; + } else if (::strcmp(key, "SelfOnly") == 0) + m_dstarSelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "BlackList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + if (::strlen(p) > 0U) { + for (unsigned int i = 0U; p[i] != 0; i++) + p[i] = ::toupper(p[i]); + std::string callsign = std::string(p); + callsign.resize(DSTAR_LONG_CALLSIGN_LENGTH, ' '); + m_dstarBlackList.push_back(callsign); + } + p = ::strtok(NULL, ",\r\n"); } - } else if (section == SECTION_DMRID_LOOKUP) { - if (::strcmp(key, "File") == 0) - m_dmrIdLookupFile = value; - else if (::strcmp(key, "Time") == 0) - m_dmrIdLookupTime = (unsigned int)::atoi(value); - } else if (section == SECTION_NXDNID_LOOKUP) { - if (::strcmp(key, "File") == 0) - m_nxdnIdLookupFile = value; - else if (::strcmp(key, "Time") == 0) - m_nxdnIdLookupTime = (unsigned int)::atoi(value); - } else if (section == SECTION_MODEM) { - if (::strcmp(key, "Port") == 0) - m_modemPort = value; - else if (::strcmp(key, "Protocol") == 0) - m_modemProtocol = value; - else if (::strcmp(key, "Speed") == 0) - m_modemSpeed = (unsigned int)::atoi(value); - else if (::strcmp(key, "Address") == 0) - m_modemAddress = (unsigned int)::strtoul(value, NULL, 16); - else if (::strcmp(key, "RXInvert") == 0) - m_modemRXInvert = ::atoi(value) == 1; - else if (::strcmp(key, "TXInvert") == 0) - m_modemTXInvert = ::atoi(value) == 1; - else if (::strcmp(key, "PTTInvert") == 0) - m_modemPTTInvert = ::atoi(value) == 1; - else if (::strcmp(key, "TXDelay") == 0) - m_ax25TXDelay = m_modemTXDelay = (unsigned int)::atoi(value); - else if (::strcmp(key, "DMRDelay") == 0) - m_modemDMRDelay = (unsigned int)::atoi(value); - else if (::strcmp(key, "RXOffset") == 0) - m_modemRXOffset = ::atoi(value); - else if (::strcmp(key, "TXOffset") == 0) - m_modemTXOffset = ::atoi(value); - else if (::strcmp(key, "RXDCOffset") == 0) - m_modemRXDCOffset = ::atoi(value); - else if (::strcmp(key, "TXDCOffset") == 0) - m_modemTXDCOffset = ::atoi(value); - else if (::strcmp(key, "RFLevel") == 0) - m_modemRFLevel = float(::atof(value)); - else if (::strcmp(key, "RXLevel") == 0) - m_modemRXLevel = float(::atof(value)); - else if (::strcmp(key, "TXLevel") == 0) - m_modemAX25TXLevel = m_modemFMTXLevel = m_modemCWIdTXLevel = m_modemDStarTXLevel = m_modemDMRTXLevel = m_modemYSFTXLevel = m_modemP25TXLevel = m_modemNXDNTXLevel = m_modemM17TXLevel = m_modemPOCSAGTXLevel = float(::atof(value)); - else if (::strcmp(key, "CWIdTXLevel") == 0) - m_modemCWIdTXLevel = float(::atof(value)); - else if (::strcmp(key, "D-StarTXLevel") == 0) - m_modemDStarTXLevel = float(::atof(value)); - else if (::strcmp(key, "DMRTXLevel") == 0) - m_modemDMRTXLevel = float(::atof(value)); - else if (::strcmp(key, "YSFTXLevel") == 0) - 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, "M17TXLevel") == 0) - m_modemM17TXLevel = float(::atof(value)); - else if (::strcmp(key, "POCSAGTXLevel") == 0) - m_modemPOCSAGTXLevel = float(::atof(value)); - else if (::strcmp(key, "FMTXLevel") == 0) - m_modemFMTXLevel = float(::atof(value)); - else if (::strcmp(key, "AX25TXLevel") == 0) - m_modemAX25TXLevel = float(::atof(value)); - else if (::strcmp(key, "RSSIMappingFile") == 0) - m_modemRSSIMappingFile = value; - else if (::strcmp(key, "Trace") == 0) - m_modemTrace = ::atoi(value) == 1; - else if (::strcmp(key, "Debug") == 0) - m_modemDebug = ::atoi(value) == 1; - } else if (section == SECTION_TRANSPARENT) { - if (::strcmp(key, "Enable") == 0) - m_transparentEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "RemoteAddress") == 0) - m_transparentRemoteAddress = value; - else if (::strcmp(key, "RemotePort") == 0) - m_transparentRemotePort = (unsigned int)::atoi(value); - else if (::strcmp(key, "LocalPort") == 0) - m_transparentLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "SendFrameType") == 0) - m_transparentSendFrameType = (unsigned int)::atoi(value); - } else if (section == SECTION_DSTAR) { - if (::strcmp(key, "Enable") == 0) - m_dstarEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Module") == 0) { - // Convert the module to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_dstarModule = value; - } else if (::strcmp(key, "SelfOnly") == 0) - m_dstarSelfOnly = ::atoi(value) == 1; - else if (::strcmp(key, "BlackList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - if (::strlen(p) > 0U) { - for (unsigned int i = 0U; p[i] != 0; i++) - p[i] = ::toupper(p[i]); - std::string callsign = std::string(p); - callsign.resize(DSTAR_LONG_CALLSIGN_LENGTH, ' '); - m_dstarBlackList.push_back(callsign); - } - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "WhiteList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - if (::strlen(p) > 0U) { - for (unsigned int i = 0U; p[i] != 0; i++) - p[i] = ::toupper(p[i]); - std::string callsign = std::string(p); - callsign.resize(DSTAR_LONG_CALLSIGN_LENGTH, ' '); - m_dstarWhiteList.push_back(callsign); - } - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "AckReply") == 0) - m_dstarAckReply = ::atoi(value) == 1; - else if (::strcmp(key, "AckTime") == 0) - m_dstarAckTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "AckMessage") == 0) - m_dstarAckMessage = ::atoi(value) == 1; - else if (::strcmp(key, "ErrorReply") == 0) - m_dstarErrorReply = ::atoi(value) == 1; - else if (::strcmp(key, "RemoteGateway") == 0) - m_dstarRemoteGateway = ::atoi(value) == 1; - else if (::strcmp(key, "ModeHang") == 0) - m_dstarModeHang = (unsigned int)::atoi(value); - } else if (section == SECTION_DMR) { - if (::strcmp(key, "Enable") == 0) - m_dmrEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Beacons") == 0) - m_dmrBeacons = ::atoi(value) == 1 ? DMR_BEACONS_NETWORK : DMR_BEACONS_OFF; - else if (::strcmp(key, "BeaconInterval") == 0) { - m_dmrBeacons = m_dmrBeacons != DMR_BEACONS_OFF ? DMR_BEACONS_TIMED : DMR_BEACONS_OFF; - m_dmrBeaconInterval = (unsigned int)::atoi(value); - } else if (::strcmp(key, "BeaconDuration") == 0) - m_dmrBeaconDuration = (unsigned int)::atoi(value); - else if (::strcmp(key, "Id") == 0) - m_dmrId = (unsigned int)::atoi(value); - else if (::strcmp(key, "ColorCode") == 0) - m_dmrColorCode = (unsigned int)::atoi(value); - else if (::strcmp(key, "SelfOnly") == 0) - m_dmrSelfOnly = ::atoi(value) == 1; - else if (::strcmp(key, "EmbeddedLCOnly") == 0) - m_dmrEmbeddedLCOnly = ::atoi(value) == 1; - else if (::strcmp(key, "DumpTAData") == 0) - m_dmrDumpTAData = ::atoi(value) == 1; - else if (::strcmp(key, "Prefixes") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int prefix = (unsigned int)::atoi(p); - if (prefix > 0U && prefix <= 999U) - m_dmrPrefixes.push_back(prefix); - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "BlackList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int id = (unsigned int)::atoi(p); - if (id > 0U) - m_dmrBlackList.push_back(id); - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "WhiteList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int id = (unsigned int)::atoi(p); - if (id > 0U) - m_dmrWhiteList.push_back(id); - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "Slot1TGWhiteList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int id = (unsigned int)::atoi(p); - if (id > 0U) - m_dmrSlot1TGWhiteList.push_back(id); - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "Slot2TGWhiteList") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int id = (unsigned int)::atoi(p); - if (id > 0U) - m_dmrSlot2TGWhiteList.push_back(id); - p = ::strtok(NULL, ",\r\n"); - } - } else if (::strcmp(key, "TXHang") == 0) - m_dmrTXHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "CallHang") == 0) - m_dmrCallHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_dmrModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "OVCM") == 0) { - switch (::atoi(value)) { + } else if (::strcmp(key, "WhiteList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + if (::strlen(p) > 0U) { + for (unsigned int i = 0U; p[i] != 0; i++) + p[i] = ::toupper(p[i]); + std::string callsign = std::string(p); + callsign.resize(DSTAR_LONG_CALLSIGN_LENGTH, ' '); + m_dstarWhiteList.push_back(callsign); + } + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "AckReply") == 0) + m_dstarAckReply = ::atoi(value) == 1; + else if (::strcmp(key, "AckTime") == 0) + m_dstarAckTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "AckMessage") == 0) + m_dstarAckMessage = ::atoi(value) == 1; + else if (::strcmp(key, "ErrorReply") == 0) + m_dstarErrorReply = ::atoi(value) == 1; + else if (::strcmp(key, "RemoteGateway") == 0) + m_dstarRemoteGateway = ::atoi(value) == 1; + else if (::strcmp(key, "ModeHang") == 0) + m_dstarModeHang = (unsigned int)::atoi(value); + } else if (section == SECTION_DMR) { + if (::strcmp(key, "Enable") == 0) + m_dmrEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Beacons") == 0) + m_dmrBeacons = ::atoi(value) == 1 ? DMR_BEACONS_NETWORK : DMR_BEACONS_OFF; + else if (::strcmp(key, "BeaconInterval") == 0) { + m_dmrBeacons = m_dmrBeacons != DMR_BEACONS_OFF ? DMR_BEACONS_TIMED : DMR_BEACONS_OFF; + m_dmrBeaconInterval = (unsigned int)::atoi(value); + } else if (::strcmp(key, "BeaconDuration") == 0) + m_dmrBeaconDuration = (unsigned int)::atoi(value); + else if (::strcmp(key, "Id") == 0) + m_dmrId = (unsigned int)::atoi(value); + else if (::strcmp(key, "ColorCode") == 0) + m_dmrColorCode = (unsigned int)::atoi(value); + else if (::strcmp(key, "SelfOnly") == 0) + m_dmrSelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "EmbeddedLCOnly") == 0) + m_dmrEmbeddedLCOnly = ::atoi(value) == 1; + else if (::strcmp(key, "DumpTAData") == 0) + m_dmrDumpTAData = ::atoi(value) == 1; + else if (::strcmp(key, "Prefixes") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int prefix = (unsigned int)::atoi(p); + if (prefix > 0U && prefix <= 999U) + m_dmrPrefixes.push_back(prefix); + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "BlackList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int id = (unsigned int)::atoi(p); + if (id > 0U) + m_dmrBlackList.push_back(id); + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "WhiteList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int id = (unsigned int)::atoi(p); + if (id > 0U) + m_dmrWhiteList.push_back(id); + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "Slot1TGWhiteList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int id = (unsigned int)::atoi(p); + if (id > 0U) + m_dmrSlot1TGWhiteList.push_back(id); + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "Slot2TGWhiteList") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int id = (unsigned int)::atoi(p); + if (id > 0U) + m_dmrSlot2TGWhiteList.push_back(id); + p = ::strtok(NULL, ",\r\n"); + } + } else if (::strcmp(key, "TXHang") == 0) + m_dmrTXHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "CallHang") == 0) + m_dmrCallHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_dmrModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "OVCM") == 0) + switch(::atoi(value)) { case 1: m_dmrOVCM = DMR_OVCM_RX_ON; break; @@ -709,392 +671,321 @@ bool CConf::read() default: m_dmrOVCM = DMR_OVCM_OFF; break; - } } - } else if (section == SECTION_FUSION) { - if (::strcmp(key, "Enable") == 0) - m_fusionEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "LowDeviation") == 0) - m_fusionLowDeviation = ::atoi(value) == 1; - else if (::strcmp(key, "RemoteGateway") == 0) - m_fusionRemoteGateway = ::atoi(value) == 1; - else if (::strcmp(key, "SelfOnly") == 0) - m_fusionSelfOnly = ::atoi(value) == 1; - else if (::strcmp(key, "TXHang") == 0) - m_fusionTXHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_fusionModeHang = (unsigned int)::atoi(value); - } else if (section == SECTION_P25) { - if (::strcmp(key, "Enable") == 0) - m_p25Enabled = ::atoi(value) == 1; - else if (::strcmp(key, "Id") == 0) - m_p25Id = (unsigned int)::atoi(value); - else if (::strcmp(key, "NAC") == 0) - m_p25NAC = (unsigned int)::strtoul(value, NULL, 16); - else if (::strcmp(key, "OverrideUIDCheck") == 0) - m_p25OverrideUID = ::atoi(value) == 1; - else if (::strcmp(key, "SelfOnly") == 0) - m_p25SelfOnly = ::atoi(value) == 1; - else if (::strcmp(key, "RemoteGateway") == 0) - m_p25RemoteGateway = ::atoi(value) == 1; - else if (::strcmp(key, "TXHang") == 0) - m_p25TXHang = (unsigned int)::atoi(value); - 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, "TXHang") == 0) - 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, "ColorCode") == 0) - m_m17ColorCode = (unsigned int)::atoi(value); - else if (::strcmp(key, "SelfOnly") == 0) - m_m17SelfOnly = ::atoi(value) == 1; - else if (::strcmp(key, "AllowEncryption") == 0) - m_m17AllowEncryption = ::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_fmEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Callsign") == 0) { - // Convert the callsign to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_fmCallsign = value; - } else if (::strcmp(key, "CallsignSpeed") == 0) - m_fmCallsignSpeed = (unsigned int)::atoi(value); - else if (::strcmp(key, "CallsignFrequency") == 0) - m_fmCallsignFrequency = (unsigned int)::atoi(value); - else if (::strcmp(key, "CallsignTime") == 0) - m_fmCallsignTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "CallsignHoldoff") == 0) - m_fmCallsignHoldoff = (unsigned int)::atoi(value); - else if (::strcmp(key, "CallsignHighLevel") == 0) - m_fmCallsignHighLevel = float(::atof(value)); - else if (::strcmp(key, "CallsignLowLevel") == 0) - m_fmCallsignLowLevel = float(::atof(value)); - else if (::strcmp(key, "CallsignAtStart") == 0) - m_fmCallsignAtStart = ::atoi(value) == 1; - else if (::strcmp(key, "CallsignAtEnd") == 0) - m_fmCallsignAtEnd = ::atoi(value) == 1; - else if (::strcmp(key, "CallsignAtLatch") == 0) - m_fmCallsignAtLatch = ::atoi(value) == 1; - else if (::strcmp(key, "RFAck") == 0) { - // Convert the ack to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_fmRFAck = value; - } else if (::strcmp(key, "ExtAck") == 0) { - // Convert the ack to upper case - for (unsigned int i = 0U; value[i] != 0; i++) - value[i] = ::toupper(value[i]); - m_fmExtAck = value; - } else if (::strcmp(key, "AckSpeed") == 0) - m_fmAckSpeed = (unsigned int)::atoi(value); - else if (::strcmp(key, "AckFrequency") == 0) - m_fmAckFrequency = (unsigned int)::atoi(value); - else if (::strcmp(key, "AckMinTime") == 0) - m_fmAckMinTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "AckDelay") == 0) - m_fmAckDelay = (unsigned int)::atoi(value); - else if (::strcmp(key, "AckLevel") == 0) - m_fmAckLevel = float(::atof(value)); - else if (::strcmp(key, "Timeout") == 0) - m_fmTimeout = (unsigned int)::atoi(value); - else if (::strcmp(key, "TimeoutLevel") == 0) - m_fmTimeoutLevel = float(::atof(value)); - else if (::strcmp(key, "CTCSSFrequency") == 0) - m_fmCTCSSFrequency = float(::atof(value)); - else if (::strcmp(key, "CTCSSThreshold") == 0) - m_fmCTCSSHighThreshold = m_fmCTCSSLowThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "CTCSSHighThreshold") == 0) - m_fmCTCSSHighThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "CTCSSLowThreshold") == 0) - m_fmCTCSSLowThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "CTCSSLevel") == 0) - m_fmCTCSSLevel = float(::atof(value)); - else if (::strcmp(key, "KerchunkTime") == 0) - m_fmKerchunkTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "HangTime") == 0) - m_fmHangTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "AccessMode") == 0) - m_fmAccessMode = ::atoi(value); - else if (::strcmp(key, "COSInvert") == 0) - m_fmCOSInvert = ::atoi(value) == 1; - else if (::strcmp(key, "NoiseSquelch") == 0) - m_fmNoiseSquelch = ::atoi(value) == 1; - else if (::strcmp(key, "SquelchThreshold") == 0) - m_fmSquelchHighThreshold = m_fmSquelchLowThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "SquelchHighThreshold") == 0) - m_fmSquelchHighThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "SquelchLowThreshold") == 0) - m_fmSquelchLowThreshold = (unsigned int)::atoi(value); - else if (::strcmp(key, "RFAudioBoost") == 0) - m_fmRFAudioBoost = (unsigned int)::atoi(value); - else if (::strcmp(key, "MaxDevLevel") == 0) - m_fmMaxDevLevel = float(::atof(value)); - else if (::strcmp(key, "ExtAudioBoost") == 0) - m_fmExtAudioBoost = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_fmModeHang = (unsigned int)::atoi(value); - } else if (section == SECTION_AX25) { - if (::strcmp(key, "Enable") == 0) - m_ax25Enabled = ::atoi(value) == 1; - else if (::strcmp(key, "TXDelay") == 0) - m_ax25TXDelay = (unsigned int)::atoi(value); - else if (::strcmp(key, "RXTwist") == 0) - m_ax25RXTwist = ::atoi(value); - else if (::strcmp(key, "SlotTime") == 0) - m_ax25SlotTime = (unsigned int)::atoi(value); - else if (::strcmp(key, "PPersist") == 0) - m_ax25PPersist = (unsigned int)::atoi(value); - else if (::strcmp(key, "Trace") == 0) - m_ax25Trace = ::atoi(value) == 1; - } else if (section == SECTION_DSTAR_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_dstarNetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "GatewayAddress") == 0) - m_dstarGatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_dstarGatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "LocalPort") == 0) - m_dstarLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_dstarNetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_dstarNetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_DMR_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_dmrNetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Type") == 0) - m_dmrNetworkType = value; - else if (::strcmp(key, "Address") == 0) - m_dmrNetworkAddress = value; - else if (::strcmp(key, "Port") == 0) - m_dmrNetworkPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "Local") == 0) - m_dmrNetworkLocal = (unsigned int)::atoi(value); - else if (::strcmp(key, "Password") == 0) - m_dmrNetworkPassword = value; - else if (::strcmp(key, "Options") == 0) - m_dmrNetworkOptions = value; - else if (::strcmp(key, "Debug") == 0) - m_dmrNetworkDebug = ::atoi(value) == 1; - else if (::strcmp(key, "Jitter") == 0) - m_dmrNetworkJitter = (unsigned int)::atoi(value); - else if (::strcmp(key, "Slot1") == 0) - m_dmrNetworkSlot1 = ::atoi(value) == 1; - else if (::strcmp(key, "Slot2") == 0) - m_dmrNetworkSlot2 = ::atoi(value) == 1; - else if (::strcmp(key, "ModeHang") == 0) - m_dmrNetworkModeHang = (unsigned int)::atoi(value); - } else if (section == SECTION_FUSION_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_fusionNetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "LocalAddress") == 0) - m_fusionNetworkMyAddress = value; - else if (::strcmp(key, "LocalPort") == 0) - m_fusionNetworkMyPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "GatewayAddress") == 0) - m_fusionNetworkGatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_fusionNetworkGatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_fusionNetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_fusionNetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_P25_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_p25NetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "GatewayAddress") == 0) - m_p25GatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_p25GatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "LocalPort") == 0) - m_p25LocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_p25NetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_p25NetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_NXDN_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_nxdnNetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "LocalAddress") == 0) - m_nxdnLocalAddress = value; - else if (::strcmp(key, "LocalPort") == 0) - m_nxdnLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "GatewayAddress") == 0) - m_nxdnGatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_nxdnGatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - 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; - else if (::strcmp(key, "LocalAddress") == 0) - m_pocsagLocalAddress = value; - else if (::strcmp(key, "LocalPort") == 0) - m_pocsagLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "GatewayAddress") == 0) - m_pocsagGatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_pocsagGatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_pocsagNetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_pocsagNetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_FM_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_fmNetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "LocalAddress") == 0) - m_fmLocalAddress = value; - else if (::strcmp(key, "LocalPort") == 0) - m_fmLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "GatewayAddress") == 0) - m_fmGatewayAddress = value; - else if (::strcmp(key, "GatewayPort") == 0) - m_fmGatewayPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "SampleRate") == 0) - m_fmSampleRate = (unsigned int)::atoi(value); - else if (::strcmp(key, "ModeHang") == 0) - m_fmNetworkModeHang = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_fmNetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_AX25_NETWORK) { - if (::strcmp(key, "Enable") == 0) - m_ax25NetworkEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Port") == 0) - m_ax25NetworkPort = value; - else if (::strcmp(key, "Speed") == 0) - m_ax25NetworkSpeed = (unsigned int)::atoi(value); - else if (::strcmp(key, "Debug") == 0) - m_ax25NetworkDebug = ::atoi(value) == 1; - } else if (section == SECTION_TFTSERIAL) { - if (::strcmp(key, "Port") == 0) - m_tftSerialPort = value; - else if (::strcmp(key, "Brightness") == 0) - m_tftSerialBrightness = (unsigned int)::atoi(value); - } else if (section == SECTION_HD44780) { - if (::strcmp(key, "Rows") == 0) - m_hd44780Rows = (unsigned int)::atoi(value); - else if (::strcmp(key, "Columns") == 0) - m_hd44780Columns = (unsigned int)::atoi(value); - else if (::strcmp(key, "I2CAddress") == 0) - m_hd44780i2cAddress = (unsigned int)::strtoul(value, NULL, 16); - else if (::strcmp(key, "PWM") == 0) - m_hd44780PWM = ::atoi(value) == 1; - else if (::strcmp(key, "PWMPin") == 0) - m_hd44780PWMPin = (unsigned int)::atoi(value); - else if (::strcmp(key, "PWMBright") == 0) - m_hd44780PWMBright = (unsigned int)::atoi(value); - else if (::strcmp(key, "PWMDim") == 0) - m_hd44780PWMDim = (unsigned int)::atoi(value); - else if (::strcmp(key, "DisplayClock") == 0) - m_hd44780DisplayClock = ::atoi(value) == 1; - else if (::strcmp(key, "UTC") == 0) - m_hd44780UTC = ::atoi(value) == 1; - else if (::strcmp(key, "Pins") == 0) { - char* p = ::strtok(value, ",\r\n"); - while (p != NULL) { - unsigned int pin = (unsigned int)::atoi(p); - m_hd44780Pins.push_back(pin); - p = ::strtok(NULL, ",\r\n"); - } - } - } else if (section == SECTION_NEXTION) { - if (::strcmp(key, "Port") == 0) - m_nextionPort = value; - else if (::strcmp(key, "Brightness") == 0) - m_nextionIdleBrightness = m_nextionBrightness = (unsigned int)::atoi(value); - else if (::strcmp(key, "DisplayClock") == 0) - m_nextionDisplayClock = ::atoi(value) == 1; - else if (::strcmp(key, "UTC") == 0) - m_nextionUTC = ::atoi(value) == 1; - else if (::strcmp(key, "IdleBrightness") == 0) - m_nextionIdleBrightness = (unsigned int)::atoi(value); - else if (::strcmp(key, "ScreenLayout") == 0) - m_nextionScreenLayout = (unsigned int)::strtoul(value, NULL, 0); - else if (::strcmp(key, "DisplayTempInFahrenheit") == 0) - m_nextionTempInFahrenheit = ::atoi(value) == 1; - } else if (section == SECTION_OLED) { - if (::strcmp(key, "Type") == 0) - m_oledType = (unsigned char)::atoi(value); - else if (::strcmp(key, "Brightness") == 0) - m_oledBrightness = (unsigned char)::atoi(value); - else if (::strcmp(key, "Invert") == 0) - m_oledInvert = ::atoi(value) == 1; - else if (::strcmp(key, "Scroll") == 0) - m_oledScroll = ::atoi(value) == 1; - else if (::strcmp(key, "Rotate") == 0) - m_oledRotate = ::atoi(value) == 1; - else if (::strcmp(key, "LogoScreensaver") == 0) - m_oledLogoScreensaver = ::atoi(value) == 1; - } else if (section == SECTION_LCDPROC) { - if (::strcmp(key, "Address") == 0) - m_lcdprocAddress = value; - else if (::strcmp(key, "Port") == 0) - m_lcdprocPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "LocalPort") == 0) - m_lcdprocLocalPort = (unsigned int)::atoi(value); - else if (::strcmp(key, "DisplayClock") == 0) - m_lcdprocDisplayClock = ::atoi(value) == 1; - else if (::strcmp(key, "UTC") == 0) - m_lcdprocUTC = ::atoi(value) == 1; - else if (::strcmp(key, "DimOnIdle") == 0) - m_lcdprocDimOnIdle = ::atoi(value) == 1; - } else if (section == SECTION_LOCK_FILE) { - if (::strcmp(key, "Enable") == 0) - m_lockFileEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "File") == 0) - m_lockFileName = value; - } else if (section == SECTION_REMOTE_CONTROL) { - if (::strcmp(key, "Enable") == 0) - m_remoteControlEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Port") == 0) - m_remoteControlPort = (unsigned int)::atoi(value); - } + } else if (section == SECTION_FUSION) { + if (::strcmp(key, "Enable") == 0) + m_fusionEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "LowDeviation") == 0) + m_fusionLowDeviation = ::atoi(value) == 1; + else if (::strcmp(key, "RemoteGateway") == 0) + m_fusionRemoteGateway = ::atoi(value) == 1; + else if (::strcmp(key, "SelfOnly") == 0) + m_fusionSelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "TXHang") == 0) + m_fusionTXHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_fusionModeHang = (unsigned int)::atoi(value); + } else if (section == SECTION_P25) { + if (::strcmp(key, "Enable") == 0) + m_p25Enabled = ::atoi(value) == 1; + else if (::strcmp(key, "Id") == 0) + m_p25Id = (unsigned int)::atoi(value); + else if (::strcmp(key, "NAC") == 0) + m_p25NAC = (unsigned int)::strtoul(value, NULL, 16); + else if (::strcmp(key, "OverrideUIDCheck") == 0) + m_p25OverrideUID = ::atoi(value) == 1; + else if (::strcmp(key, "SelfOnly") == 0) + m_p25SelfOnly = ::atoi(value) == 1; + else if (::strcmp(key, "RemoteGateway") == 0) + m_p25RemoteGateway = ::atoi(value) == 1; + else if (::strcmp(key, "TXHang") == 0) + m_p25TXHang = (unsigned int)::atoi(value); + 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, "TXHang") == 0) + m_nxdnTXHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_nxdnModeHang = (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_fmEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Callsign") == 0) { + // Convert the callsign to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_fmCallsign = value; + } else if (::strcmp(key, "CallsignSpeed") == 0) + m_fmCallsignSpeed = (unsigned int)::atoi(value); + else if (::strcmp(key, "CallsignFrequency") == 0) + m_fmCallsignFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "CallsignTime") == 0) + m_fmCallsignTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "CallsignHoldoff") == 0) + m_fmCallsignHoldoff = (unsigned int)::atoi(value); + else if (::strcmp(key, "CallsignHighLevel") == 0) + m_fmCallsignHighLevel = float(::atof(value)); + else if (::strcmp(key, "CallsignLowLevel") == 0) + m_fmCallsignLowLevel = float(::atof(value)); + else if (::strcmp(key, "CallsignAtStart") == 0) + m_fmCallsignAtStart = ::atoi(value) == 1; + else if (::strcmp(key, "CallsignAtEnd") == 0) + m_fmCallsignAtEnd = ::atoi(value) == 1; + else if (::strcmp(key, "CallsignAtLatch") == 0) + m_fmCallsignAtLatch = ::atoi(value) == 1; + else if (::strcmp(key, "RFAck") == 0) { + // Convert the ack to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_fmRFAck = value; + } else if (::strcmp(key, "ExtAck") == 0) { + // Convert the ack to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_fmExtAck = value; + } else if (::strcmp(key, "AckSpeed") == 0) + m_fmAckSpeed = (unsigned int)::atoi(value); + else if (::strcmp(key, "AckFrequency") == 0) + m_fmAckFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "AckMinTime") == 0) + m_fmAckMinTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "AckDelay") == 0) + m_fmAckDelay = (unsigned int)::atoi(value); + else if (::strcmp(key, "AckLevel") == 0) + m_fmAckLevel = float(::atof(value)); + else if (::strcmp(key, "Timeout") == 0) + m_fmTimeout = (unsigned int)::atoi(value); + else if (::strcmp(key, "TimeoutLevel") == 0) + m_fmTimeoutLevel = float(::atof(value)); + else if (::strcmp(key, "CTCSSFrequency") == 0) + m_fmCTCSSFrequency = float(::atof(value)); + else if (::strcmp(key, "CTCSSThreshold") == 0) + m_fmCTCSSHighThreshold = m_fmCTCSSLowThreshold = (unsigned int)::atoi(value); + else if (::strcmp(key, "CTCSSHighThreshold") == 0) + m_fmCTCSSHighThreshold = (unsigned int)::atoi(value); + else if (::strcmp(key, "CTCSSLowThreshold") == 0) + m_fmCTCSSLowThreshold = (unsigned int)::atoi(value); + else if (::strcmp(key, "CTCSSLevel") == 0) + m_fmCTCSSLevel = float(::atof(value)); + else if (::strcmp(key, "KerchunkTime") == 0) + m_fmKerchunkTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "HangTime") == 0) + m_fmHangTime = (unsigned int)::atoi(value); + else if (::strcmp(key, "AccessMode") == 0) + m_fmAccessMode = (unsigned int)::atoi(value); + else if (::strcmp(key, "COSInvert") == 0) + m_fmCOSInvert = ::atoi(value) == 1; + else if (::strcmp(key, "RFAudioBoost") == 0) + m_fmRFAudioBoost = (unsigned int)::atoi(value); + else if (::strcmp(key, "MaxDevLevel") == 0) + m_fmMaxDevLevel = float(::atof(value)); + else if (::strcmp(key, "ExtAudioBoost") == 0) + m_fmExtAudioBoost = (unsigned int)::atoi(value); + } else if (section == SECTION_DSTAR_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_dstarNetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "GatewayAddress") == 0) + m_dstarGatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_dstarGatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "LocalPort") == 0) + m_dstarLocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_dstarNetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_dstarNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_DMR_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_dmrNetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Type") == 0) + m_dmrNetworkType = value; + else if (::strcmp(key, "Address") == 0) + m_dmrNetworkAddress = value; + else if (::strcmp(key, "Port") == 0) + m_dmrNetworkPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Local") == 0) + m_dmrNetworkLocal = (unsigned int)::atoi(value); + else if (::strcmp(key, "Password") == 0) + m_dmrNetworkPassword = value; + else if (::strcmp(key, "Options") == 0) + m_dmrNetworkOptions = value; + else if (::strcmp(key, "Debug") == 0) + m_dmrNetworkDebug = ::atoi(value) == 1; + else if (::strcmp(key, "Jitter") == 0) + m_dmrNetworkJitter = (unsigned int)::atoi(value); + else if (::strcmp(key, "Slot1") == 0) + m_dmrNetworkSlot1 = ::atoi(value) == 1; + else if (::strcmp(key, "Slot2") == 0) + m_dmrNetworkSlot2 = ::atoi(value) == 1; + else if (::strcmp(key, "ModeHang") == 0) + m_dmrNetworkModeHang = (unsigned int)::atoi(value); + } else if (section == SECTION_FUSION_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_fusionNetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "LocalAddress") == 0) + m_fusionNetworkMyAddress = value; + else if (::strcmp(key, "LocalPort") == 0) + m_fusionNetworkMyPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "GatewayAddress") == 0) + m_fusionNetworkGatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_fusionNetworkGatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_fusionNetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_fusionNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_P25_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_p25NetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "GatewayAddress") == 0) + m_p25GatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_p25GatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "LocalPort") == 0) + m_p25LocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_p25NetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_p25NetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_NXDN_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_nxdnNetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Protocol") == 0) + m_nxdnNetworkProtocol = value; + else if (::strcmp(key, "LocalAddress") == 0) + m_nxdnLocalAddress = value; + else if (::strcmp(key, "LocalPort") == 0) + m_nxdnLocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "GatewayAddress") == 0) + m_nxdnGatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_nxdnGatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_nxdnNetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_nxdnNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_POCSAG_NETWORK) { + if (::strcmp(key, "Enable") == 0) + m_pocsagNetworkEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "LocalAddress") == 0) + m_pocsagLocalAddress = value; + else if (::strcmp(key, "LocalPort") == 0) + m_pocsagLocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "GatewayAddress") == 0) + m_pocsagGatewayAddress = value; + else if (::strcmp(key, "GatewayPort") == 0) + m_pocsagGatewayPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "ModeHang") == 0) + m_pocsagNetworkModeHang = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_pocsagNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_TFTSERIAL) { + if (::strcmp(key, "Port") == 0) + m_tftSerialPort = value; + else if (::strcmp(key, "Brightness") == 0) + m_tftSerialBrightness = (unsigned int)::atoi(value); + } else if (section == SECTION_HD44780) { + if (::strcmp(key, "Rows") == 0) + m_hd44780Rows = (unsigned int)::atoi(value); + else if (::strcmp(key, "Columns") == 0) + m_hd44780Columns = (unsigned int)::atoi(value); + else if (::strcmp(key, "I2CAddress") == 0) + m_hd44780i2cAddress = (unsigned int)::strtoul(value, NULL, 16); + else if (::strcmp(key, "PWM") == 0) + m_hd44780PWM = ::atoi(value) == 1; + else if (::strcmp(key, "PWMPin") == 0) + m_hd44780PWMPin = (unsigned int)::atoi(value); + else if (::strcmp(key, "PWMBright") == 0) + m_hd44780PWMBright = (unsigned int)::atoi(value); + else if (::strcmp(key, "PWMDim") == 0) + m_hd44780PWMDim = (unsigned int)::atoi(value); + else if (::strcmp(key, "DisplayClock") == 0) + m_hd44780DisplayClock = ::atoi(value) == 1; + else if (::strcmp(key, "UTC") == 0) + m_hd44780UTC = ::atoi(value) == 1; + else if (::strcmp(key, "Pins") == 0) { + char* p = ::strtok(value, ",\r\n"); + while (p != NULL) { + unsigned int pin = (unsigned int)::atoi(p); + m_hd44780Pins.push_back(pin); + p = ::strtok(NULL, ",\r\n"); + } + } + } else if (section == SECTION_NEXTION) { + if (::strcmp(key, "Port") == 0) + m_nextionPort = value; + else if (::strcmp(key, "Brightness") == 0) + m_nextionIdleBrightness = m_nextionBrightness = (unsigned int)::atoi(value); + else if (::strcmp(key, "DisplayClock") == 0) + m_nextionDisplayClock = ::atoi(value) == 1; + else if (::strcmp(key, "UTC") == 0) + m_nextionUTC = ::atoi(value) == 1; + else if (::strcmp(key, "IdleBrightness") == 0) + m_nextionIdleBrightness = (unsigned int)::atoi(value); + else if (::strcmp(key, "ScreenLayout") == 0) + m_nextionScreenLayout = (unsigned int)::strtoul(value, NULL, 0); + else if (::strcmp(key, "DisplayTempInFahrenheit") == 0) + m_nextionTempInFahrenheit = ::atoi(value) == 1; + } else if (section == SECTION_OLED) { + if (::strcmp(key, "Type") == 0) + m_oledType = (unsigned char)::atoi(value); + else if (::strcmp(key, "Brightness") == 0) + m_oledBrightness = (unsigned char)::atoi(value); + else if (::strcmp(key, "Invert") == 0) + m_oledInvert = ::atoi(value) == 1; + else if (::strcmp(key, "Scroll") == 0) + m_oledScroll = ::atoi(value) == 1; + else if (::strcmp(key, "Rotate") == 0) + m_oledRotate = ::atoi(value) == 1; + else if (::strcmp(key, "LogoScreensaver") == 0) + m_oledLogoScreensaver = ::atoi(value) == 1; + } else if (section == SECTION_LCDPROC) { + if (::strcmp(key, "Address") == 0) + m_lcdprocAddress = value; + else if (::strcmp(key, "Port") == 0) + m_lcdprocPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "LocalPort") == 0) + m_lcdprocLocalPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "DisplayClock") == 0) + m_lcdprocDisplayClock = ::atoi(value) == 1; + else if (::strcmp(key, "UTC") == 0) + m_lcdprocUTC = ::atoi(value) == 1; + else if (::strcmp(key, "DimOnIdle") == 0) + m_lcdprocDimOnIdle = ::atoi(value) == 1; + } else if (section == SECTION_LOCK_FILE) { + if (::strcmp(key, "Enable") == 0) + m_lockFileEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "File") == 0) + m_lockFileName = value; + } else if (section == SECTION_REMOTE_CONTROL) { + if (::strcmp(key, "Enable") == 0) + m_remoteControlEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Address") == 0) + m_remoteControlAddress = value; + else if (::strcmp(key, "Port") == 0) + m_remoteControlPort = (unsigned int)::atoi(value); + } + } - ::fclose(fp); + ::fclose(fp); - return true; + return true; } std::string CConf::getCallsign() const @@ -1242,11 +1133,6 @@ std::string CConf::getModemProtocol() const return m_modemProtocol; } -unsigned int CConf::getModemSpeed() const -{ - return m_modemSpeed; -} - unsigned int CConf::getModemAddress() const { return m_modemAddress; @@ -1337,11 +1223,6 @@ float CConf::getModemNXDNTXLevel() const return m_modemNXDNTXLevel; } -float CConf::getModemM17TXLevel() const -{ - return m_modemM17TXLevel; -} - float CConf::getModemPOCSAGTXLevel() const { return m_modemPOCSAGTXLevel; @@ -1352,11 +1233,6 @@ float CConf::getModemFMTXLevel() const return m_modemFMTXLevel; } -float CConf::getModemAX25TXLevel() const -{ - return m_modemAX25TXLevel; -} - std::string CConf::getModemRSSIMappingFile () const { return m_modemRSSIMappingFile; @@ -1402,6 +1278,16 @@ unsigned int CConf::getTransparentSendFrameType() const return m_transparentSendFrameType; } +bool CConf::getUMPEnabled() const +{ + return m_umpEnabled; +} + +std::string CConf::getUMPPort() const +{ + return m_umpPort; +} + bool CConf::getDStarEnabled() const { return m_dstarEnabled; @@ -1652,36 +1538,6 @@ unsigned int CConf::getNXDNModeHang() const return m_nxdnModeHang; } -bool CConf::getM17Enabled() const -{ - return m_m17Enabled; -} - -unsigned int CConf::getM17ColorCode() const -{ - return m_m17ColorCode; -} - -bool CConf::getM17SelfOnly() const -{ - return m_m17SelfOnly; -} - -bool CConf::getM17AllowEncryption() const -{ - return m_m17AllowEncryption; -} - -unsigned int CConf::getM17TXHang() const -{ - return m_m17TXHang; -} - -unsigned int CConf::getM17ModeHang() const -{ - return m_m17ModeHang; -} - bool CConf::getPOCSAGEnabled() const { return m_pocsagEnabled; @@ -1832,21 +1688,6 @@ bool CConf::getFMCOSInvert() const return m_fmCOSInvert; } -bool CConf::getFMNoiseSquelch() const -{ - return m_fmNoiseSquelch; -} - -unsigned int CConf::getFMSquelchHighThreshold() const -{ - return m_fmSquelchHighThreshold; -} - -unsigned int CConf::getFMSquelchLowThreshold() const -{ - return m_fmSquelchLowThreshold; -} - unsigned int CConf::getFMRFAudioBoost() const { return m_fmRFAudioBoost; @@ -1862,41 +1703,6 @@ unsigned int CConf::getFMExtAudioBoost() const return m_fmExtAudioBoost; } -unsigned int CConf::getFMModeHang() const -{ - return m_fmModeHang; -} - -bool CConf::getAX25Enabled() const -{ - return m_ax25Enabled; -} - -unsigned int CConf::getAX25TXDelay() const -{ - return m_ax25TXDelay; -} - -int CConf::getAX25RXTwist() const -{ - return m_ax25RXTwist; -} - -unsigned int CConf::getAX25SlotTime() const -{ - return m_ax25SlotTime; -} - -unsigned int CConf::getAX25PPersist() const -{ - return m_ax25PPersist; -} - -bool CConf::getAX25Trace() const -{ - return m_ax25Trace; -} - bool CConf::getDStarNetworkEnabled() const { return m_dstarNetworkEnabled; @@ -2092,36 +1898,6 @@ 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; @@ -2157,66 +1933,6 @@ bool CConf::getPOCSAGNetworkDebug() const return m_pocsagNetworkDebug; } -bool CConf::getFMNetworkEnabled() const -{ - return m_fmNetworkEnabled; -} - -std::string CConf::getFMGatewayAddress() const -{ - return m_fmGatewayAddress; -} - -unsigned int CConf::getFMGatewayPort() const -{ - return m_fmGatewayPort; -} - -std::string CConf::getFMLocalAddress() const -{ - return m_fmLocalAddress; -} - -unsigned int CConf::getFMLocalPort() const -{ - return m_fmLocalPort; -} - -unsigned int CConf::getFMSampleRate() const -{ - return m_fmSampleRate; -} - -unsigned int CConf::getFMNetworkModeHang() const -{ - return m_fmNetworkModeHang; -} - -bool CConf::getFMNetworkDebug() const -{ - return m_fmNetworkDebug; -} - -bool CConf::getAX25NetworkEnabled() const -{ - return m_ax25NetworkEnabled; -} - -std::string CConf::getAX25NetworkPort() const -{ - return m_ax25NetworkPort; -} - -unsigned int CConf::getAX25NetworkSpeed() const -{ - return m_ax25NetworkSpeed; -} - -bool CConf::getAX25NetworkDebug() const -{ - return m_ax25NetworkDebug; -} - std::string CConf::getTFTSerialPort() const { return m_tftSerialPort; @@ -2337,6 +2053,7 @@ bool CConf::getOLEDLogoScreensaver() const return m_oledLogoScreensaver; } + std::string CConf::getLCDprocAddress() const { return m_lcdprocAddress; diff --git a/Conf.h b/Conf.h index 92fa8ce..da2f78e 100644 --- a/Conf.h +++ b/Conf.h @@ -72,7 +72,6 @@ public: // The Modem section std::string getModemPort() const; std::string getModemProtocol() const; - unsigned int getModemSpeed() const; unsigned int getModemAddress() const; bool getModemRXInvert() const; bool getModemTXInvert() const; @@ -91,10 +90,8 @@ public: float getModemYSFTXLevel() const; float getModemP25TXLevel() const; float getModemNXDNTXLevel() const; - float getModemM17TXLevel() const; float getModemPOCSAGTXLevel() const; float getModemFMTXLevel() const; - float getModemAX25TXLevel() const; std::string getModemRSSIMappingFile() const; bool getModemUseCOSAsLockout() const; bool getModemTrace() const; @@ -107,6 +104,10 @@ public: unsigned int getTransparentLocalPort() const; unsigned int getTransparentSendFrameType() const; + // The UMP section + bool getUMPEnabled() const; + std::string getUMPPort() const; + // The D-Star section bool getDStarEnabled() const; std::string getDStarModule() const; @@ -167,26 +168,10 @@ public: unsigned int getNXDNTXHang() const; unsigned int getNXDNModeHang() const; - // The M17 section - bool getM17Enabled() const; - unsigned int getM17ColorCode() const; - bool getM17SelfOnly() const; - bool getM17AllowEncryption() const; - unsigned int getM17TXHang() const; - unsigned int getM17ModeHang() const; - // The POCSAG section bool getPOCSAGEnabled() const; unsigned int getPOCSAGFrequency() const; - // The AX.25 section - bool getAX25Enabled() const; - unsigned int getAX25TXDelay() const; - int getAX25RXTwist() const; - unsigned int getAX25SlotTime() const; - unsigned int getAX25PPersist() const; - bool getAX25Trace() const; - // The FM Section bool getFMEnabled() const; std::string getFMCallsign() const; @@ -216,13 +201,9 @@ public: unsigned int getFMHangTime() const; unsigned int getFMAccessMode() const; bool getFMCOSInvert() const; - bool getFMNoiseSquelch() const; - unsigned int getFMSquelchHighThreshold() const; - unsigned int getFMSquelchLowThreshold() const; unsigned int getFMRFAudioBoost() const; float getFMMaxDevLevel() const; unsigned int getFMExtAudioBoost() const; - unsigned int getFMModeHang() const; // The D-Star Network section bool getDStarNetworkEnabled() const; @@ -273,14 +254,6 @@ 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; @@ -290,22 +263,6 @@ public: unsigned int getPOCSAGNetworkModeHang() const; bool getPOCSAGNetworkDebug() const; - // The FM Network section - bool getFMNetworkEnabled() const; - std::string getFMGatewayAddress() const; - unsigned int getFMGatewayPort() const; - std::string getFMLocalAddress() const; - unsigned int getFMLocalPort() const; - unsigned int getFMSampleRate() const; - unsigned int getFMNetworkModeHang() const; - bool getFMNetworkDebug() const; - - // The AX.25 Network section - bool getAX25NetworkEnabled() const; - std::string getAX25NetworkPort() const; - unsigned int getAX25NetworkSpeed() const; - bool getAX25NetworkDebug() const; - // The TFTSERIAL section std::string getTFTSerialPort() const; unsigned int getTFTSerialBrightness() const; @@ -393,7 +350,6 @@ private: std::string m_modemPort; std::string m_modemProtocol; - unsigned int m_modemSpeed; unsigned int m_modemAddress; bool m_modemRXInvert; bool m_modemTXInvert; @@ -412,10 +368,8 @@ private: float m_modemYSFTXLevel; float m_modemP25TXLevel; float m_modemNXDNTXLevel; - float m_modemM17TXLevel; float m_modemPOCSAGTXLevel; float m_modemFMTXLevel; - float m_modemAX25TXLevel; std::string m_modemRSSIMappingFile; bool m_modemUseCOSAsLockout; bool m_modemTrace; @@ -427,6 +381,9 @@ private: unsigned int m_transparentLocalPort; unsigned int m_transparentSendFrameType; + bool m_umpEnabled; + std::string m_umpPort; + bool m_dstarEnabled; std::string m_dstarModule; bool m_dstarSelfOnly; @@ -482,13 +439,6 @@ private: unsigned int m_nxdnTXHang; unsigned int m_nxdnModeHang; - bool m_m17Enabled; - unsigned int m_m17ColorCode; - bool m_m17SelfOnly; - bool m_m17AllowEncryption; - unsigned int m_m17TXHang; - unsigned int m_m17ModeHang; - bool m_pocsagEnabled; unsigned int m_pocsagFrequency; @@ -520,20 +470,9 @@ private: unsigned int m_fmHangTime; unsigned int m_fmAccessMode; bool m_fmCOSInvert; - bool m_fmNoiseSquelch; - unsigned int m_fmSquelchHighThreshold; - unsigned int m_fmSquelchLowThreshold; unsigned int m_fmRFAudioBoost; float m_fmMaxDevLevel; unsigned int m_fmExtAudioBoost; - unsigned int m_fmModeHang; - - bool m_ax25Enabled; - unsigned int m_ax25TXDelay; - int m_ax25RXTwist; - unsigned int m_ax25SlotTime; - unsigned int m_ax25PPersist; - bool m_ax25Trace; bool m_dstarNetworkEnabled; std::string m_dstarGatewayAddress; @@ -579,13 +518,6 @@ 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; @@ -594,20 +526,6 @@ private: unsigned int m_pocsagNetworkModeHang; bool m_pocsagNetworkDebug; - bool m_fmNetworkEnabled; - std::string m_fmGatewayAddress; - unsigned int m_fmGatewayPort; - std::string m_fmLocalAddress; - unsigned int m_fmLocalPort; - unsigned int m_fmSampleRate; - unsigned int m_fmNetworkModeHang; - bool m_fmNetworkDebug; - - bool m_ax25NetworkEnabled; - std::string m_ax25NetworkPort; - unsigned int m_ax25NetworkSpeed; - bool m_ax25NetworkDebug; - std::string m_tftSerialPort; unsigned int m_tftSerialBrightness; diff --git a/DMRControl.cpp b/DMRControl.cpp index a5efb21..18ababd 100644 --- a/DMRControl.cpp +++ b/DMRControl.cpp @@ -21,7 +21,7 @@ #include #include -CDMRControl::CDMRControl(unsigned int id, unsigned int colorCode, unsigned int callHang, bool selfOnly, bool embeddedLCOnly, bool dumpTAData, const std::vector& prefixes, const std::vector& blacklist, const std::vector& whitelist, const std::vector& slot1TGWhitelist, const std::vector& slot2TGWhitelist, unsigned int timeout, IModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssi, unsigned int jitter, DMR_OVCM_TYPES ovcm) : +CDMRControl::CDMRControl(unsigned int id, unsigned int colorCode, unsigned int callHang, bool selfOnly, bool embeddedLCOnly, bool dumpTAData, const std::vector& prefixes, const std::vector& blacklist, const std::vector& whitelist, const std::vector& slot1TGWhitelist, const std::vector& slot2TGWhitelist, unsigned int timeout, CModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssi, unsigned int jitter, DMR_OVCM_TYPES ovcm) : m_colorCode(colorCode), m_modem(modem), m_network(network), diff --git a/DMRControl.h b/DMRControl.h index 31f5f7f..438a9f3 100644 --- a/DMRControl.h +++ b/DMRControl.h @@ -31,7 +31,7 @@ class CDMRControl { public: - CDMRControl(unsigned int id, unsigned int colorCode, unsigned int callHang, bool selfOnly, bool embeddedLCOnly, bool dumpTAData, const std::vector& prefixes, const std::vector& blacklist, const std::vector& whitelist, const std::vector& slot1TGWhitelist, const std::vector& slot2TGWhitelist, unsigned int timeout, IModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssi, unsigned int jitter, DMR_OVCM_TYPES ovcm); + CDMRControl(unsigned int id, unsigned int colorCode, unsigned int callHang, bool selfOnly, bool embeddedLCOnly, bool dumpTAData, const std::vector& prefixes, const std::vector& blacklist, const std::vector& whitelist, const std::vector& slot1TGWhitelist, const std::vector& slot2TGWhitelist, unsigned int timeout, CModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssi, unsigned int jitter, DMR_OVCM_TYPES ovcm); ~CDMRControl(); bool processWakeup(const unsigned char* data); @@ -50,7 +50,7 @@ public: private: unsigned int m_colorCode; - IModem* m_modem; + CModem* m_modem; IDMRNetwork* m_network; CDMRSlot m_slot1; CDMRSlot m_slot2; diff --git a/DMRSlot.cpp b/DMRSlot.cpp index 4209860..c21f4ca 100644 --- a/DMRSlot.cpp +++ b/DMRSlot.cpp @@ -37,7 +37,7 @@ unsigned int CDMRSlot::m_colorCode = 0U; bool CDMRSlot::m_embeddedLCOnly = false; bool CDMRSlot::m_dumpTAData = true; -IModem* CDMRSlot::m_modem = NULL; +CModem* CDMRSlot::m_modem = NULL; IDMRNetwork* CDMRSlot::m_network = NULL; CDisplay* CDMRSlot::m_display = NULL; bool CDMRSlot::m_duplex = true; @@ -1896,7 +1896,7 @@ void CDMRSlot::writeQueueNet(const unsigned char *data) m_queue.addData(data, len); } -void CDMRSlot::init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, IModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM_TYPES ovcm) +void CDMRSlot::init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM_TYPES ovcm) { assert(modem != NULL); assert(display != NULL); diff --git a/DMRSlot.h b/DMRSlot.h index 789e6ce..7a4c804 100644 --- a/DMRSlot.h +++ b/DMRSlot.h @@ -62,7 +62,7 @@ public: void enable(bool enabled); - static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, IModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM_TYPES ovcm); + static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, IDMRNetwork* network, CDisplay* display, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM_TYPES ovcm); private: unsigned int m_slotNo; @@ -117,7 +117,7 @@ private: static bool m_embeddedLCOnly; static bool m_dumpTAData; - static IModem* m_modem; + static CModem* m_modem; static IDMRNetwork* m_network; static CDisplay* m_display; static bool m_duplex; diff --git a/DStarControl.cpp b/DStarControl.cpp index 8eafe7a..794b748 100644 --- a/DStarControl.cpp +++ b/DStarControl.cpp @@ -101,8 +101,8 @@ m_netSkipDTMFBlankingFrames(0U) m_gateway = new unsigned char[DSTAR_LONG_CALLSIGN_LENGTH]; m_lastFrame = new unsigned char[DSTAR_FRAME_LENGTH_BYTES + 1U]; - m_rfVoiceSyncData = new unsigned char[DSTAR_MODEM_DATA_LEN]; - m_netVoiceSyncData = new unsigned char[DSTAR_MODEM_DATA_LEN]; + m_rfVoiceSyncData = new unsigned char[MODEM_DATA_LEN]; + m_netVoiceSyncData = new unsigned char[MODEM_DATA_LEN]; std::string call = callsign; call.resize(DSTAR_LONG_CALLSIGN_LENGTH - 1U, ' '); @@ -149,7 +149,7 @@ unsigned int CDStarControl::maybeFixupVoiceFrame( unsigned char mini_header_type = mini_header & DSTAR_SLOW_DATA_TYPE_MASK; if (n == 0U) { - ::memcpy(voice_sync_data, data, DSTAR_MODEM_DATA_LEN); + ::memcpy(voice_sync_data, data, MODEM_DATA_LEN); *voice_sync_data_len = len; } else if ((n % 2U != 0U) && ((mini_header_type == DSTAR_SLOW_DATA_TYPE_FASTDATA01) || diff --git a/Defines.h b/Defines.h index 511ee8b..ebd75eb 100644 --- a/Defines.h +++ b/Defines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020 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 @@ -26,7 +26,6 @@ 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; @@ -40,7 +39,7 @@ const unsigned char TAG_DATA = 0x01U; const unsigned char TAG_LOST = 0x02U; const unsigned char TAG_EOT = 0x03U; -const unsigned int DSTAR_MODEM_DATA_LEN = 220U; +const unsigned int MODEM_DATA_LEN = 220U; enum HW_TYPE { HWT_MMDVM, diff --git a/Display.cpp b/Display.cpp index 7d4b1a9..39b0a09 100644 --- a/Display.cpp +++ b/Display.cpp @@ -21,12 +21,14 @@ #include "SerialController.h" #include "ModemSerialPort.h" #include "NullDisplay.h" +#include "TFTSerial.h" #include "TFTSurenoo.h" #include "LCDproc.h" #include "Nextion.h" #include "CASTInfo.h" #include "Conf.h" #include "Modem.h" +#include "UMP.h" #include "Log.h" #if defined(HD44780) @@ -333,40 +335,6 @@ void CDisplay::clearNXDN() } } -void CDisplay::writeM17(const char* source, const char* dest, const char* type) -{ - assert(source != NULL); - assert(dest != NULL); - assert(type != NULL); - - m_timer1.start(); - m_mode1 = MODE_IDLE; - - writeM17Int(source, dest, type); -} - -void CDisplay::writeM17RSSI(unsigned char rssi) -{ - if (rssi != 0U) - writeM17RSSIInt(rssi); -} - -void CDisplay::writeM17BER(float ber) -{ - writeM17BERInt(ber); -} - -void CDisplay::clearM17() -{ - if (m_timer1.hasExpired()) { - clearM17Int(); - m_timer1.stop(); - m_mode1 = MODE_IDLE; - } else { - m_mode1 = MODE_M17; - } -} - void CDisplay::writePOCSAG(uint32_t ric, const std::string& message) { m_timer1.start(); @@ -424,11 +392,6 @@ void CDisplay::clock(unsigned int ms) m_mode1 = MODE_IDLE; m_timer1.stop(); break; - case MODE_M17: - clearM17Int(); - m_mode1 = MODE_IDLE; - m_timer1.stop(); - break; case MODE_POCSAG: clearPOCSAGInt(); m_mode1 = MODE_IDLE; @@ -519,14 +482,6 @@ void CDisplay::writeNXDNBERInt(float ber) { } -void CDisplay::writeM17RSSIInt(unsigned char rssi) -{ -} - -void CDisplay::writeM17BERInt(float ber) -{ -} - int CDisplay::writeNXDNIntEx(const class CUserDBentry& source, bool group, unsigned int dest, const char* type) { /* return value definition is same as writeDMRIntEx() */ @@ -535,17 +490,17 @@ int CDisplay::writeNXDNIntEx(const class CUserDBentry& source, bool group, unsig /* Factory method extracted from MMDVMHost.cpp - BG5HHP */ -CDisplay* CDisplay::createDisplay(const CConf& conf, IModem* modem) +CDisplay* CDisplay::createDisplay(const CConf& conf, CUMP* ump, CModem* modem) { - CDisplay *display = NULL; + CDisplay *display = NULL; - std::string type = conf.getDisplay(); + std::string type = conf.getDisplay(); unsigned int dmrid = conf.getDMRId(); LogInfo("Display Parameters"); LogInfo(" Type: %s", type.c_str()); - if (type == "TFT Surenoo") { + if (type == "TFT Serial" || type == "TFT Surenoo") { std::string port = conf.getTFTSerialPort(); unsigned int brightness = conf.getTFTSerialBrightness(); @@ -554,11 +509,14 @@ CDisplay* CDisplay::createDisplay(const CConf& conf, IModem* modem) ISerialPort* serial = NULL; if (port == "modem") - serial = new IModemSerialPort(modem); + serial = new CModemSerialPort(modem); else - serial = new CSerialController(port, 115200U); + serial = new CSerialController(port, (type == "TFT Serial") ? SERIAL_9600 : SERIAL_115200); - display = new CTFTSurenoo(conf.getCallsign(), dmrid, serial, brightness, conf.getDuplex()); + if (type == "TFT Surenoo") + display = new CTFTSurenoo(conf.getCallsign(), dmrid, serial, brightness, conf.getDuplex()); + else + display = new CTFTSerial(conf.getCallsign(), dmrid, serial, brightness); } else if (type == "Nextion") { std::string port = conf.getNextionPort(); unsigned int brightness = conf.getNextionBrightness(); @@ -597,14 +555,21 @@ CDisplay* CDisplay::createDisplay(const CConf& conf, IModem* modem) } if (port == "modem") { - ISerialPort* serial = new IModemSerialPort(modem); + ISerialPort* serial = new CModemSerialPort(modem); display = new CNextion(conf.getCallsign(), dmrid, serial, brightness, displayClock, utc, idleBrightness, screenLayout, txFrequency, rxFrequency, displayTempInF); + } else if (port == "ump") { + if (ump != NULL) { + display = new CNextion(conf.getCallsign(), dmrid, ump, brightness, displayClock, utc, idleBrightness, screenLayout, txFrequency, rxFrequency, displayTempInF); + } else { + LogInfo(" NullDisplay loaded"); + display = new CNullDisplay; + } } else { - unsigned int baudrate = 9600U; - if (screenLayout == 4U) - baudrate = 115200U; + SERIAL_SPEED baudrate = SERIAL_9600; + if (screenLayout&0x0cU) + baudrate = SERIAL_115200; - LogInfo(" Display baudrate: %u ", baudrate); + LogInfo(" Display baudrate: %u ",baudrate); ISerialPort* serial = new CSerialController(port, baudrate); display = new CNextion(conf.getCallsign(), dmrid, serial, brightness, displayClock, utc, idleBrightness, screenLayout, txFrequency, rxFrequency, displayTempInF); } diff --git a/Display.h b/Display.h index c5e7d72..e23b3ec 100644 --- a/Display.h +++ b/Display.h @@ -21,14 +21,14 @@ #include "Timer.h" #include "UserDBentry.h" -#include "Modem.h" #include #include class CConf; -class IModem; +class CModem; +class CUMP; class CDisplay { @@ -72,11 +72,6 @@ public: void writeNXDNBER(float ber); void clearNXDN(); - void writeM17(const char* source, const char* dest, const char* type); - void writeM17RSSI(unsigned char rssi); - void writeM17BER(float ber); - void clearM17(); - void writePOCSAG(uint32_t ric, const std::string& message); void clearPOCSAG(); @@ -86,7 +81,7 @@ public: void clock(unsigned int ms); - static CDisplay* createDisplay(const CConf& conf, IModem* modem); + static CDisplay* createDisplay(const CConf& conf, CUMP* ump, CModem* modem); protected: virtual void setIdleInt() = 0; @@ -123,11 +118,6 @@ protected: virtual void writeNXDNBERInt(float ber); virtual void clearNXDNInt() = 0; - virtual void writeM17Int(const char* source, const char* dest, const char* type) = 0; - virtual void writeM17RSSIInt(unsigned char rssi); - virtual void writeM17BERInt(float ber); - virtual void clearM17Int() = 0; - virtual void writePOCSAGInt(uint32_t ric, const std::string& message) = 0; virtual void clearPOCSAGInt() = 0; diff --git a/FMControl.cpp b/FMControl.cpp deleted file mode 100644 index 9a2a78a..0000000 --- a/FMControl.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 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 "FMControl.h" - -#include - -#if defined(DUMP_RF_AUDIO) -#include -#endif - -#define SWAP_BYTES_16(a) (((a >> 8) & 0x00FFU) | ((a << 8) & 0xFF00U)) - -const float DEEMPHASIS_GAIN_DB = 0.0F; -const float PREEMPHASIS_GAIN_DB = 13.0F; -const float FILTER_GAIN_DB = 0.0F; -const unsigned int FM_MASK = 0x00000FFFU; - -CFMControl::CFMControl(CFMNetwork* network) : -m_network(network), -m_enabled(false), -m_incomingRFAudio(1600U, "Incoming RF FM Audio"), -m_preemphasis (NULL), -m_deemphasis (NULL), -m_filterStage1(NULL), -m_filterStage2(NULL), -m_filterStage3(NULL) -{ - m_preemphasis = new CIIRDirectForm1Filter(8.315375384336983F,-7.03334621603483F,0.0F,1.0F,0.282029168302153F,0.0F, PREEMPHASIS_GAIN_DB); - m_deemphasis = new CIIRDirectForm1Filter(0.07708787090460224F,0.07708787090460224F,0.0F,1.0F,-0.8458242581907955F,0.0F, DEEMPHASIS_GAIN_DB); - - //cheby type 1 0.2dB cheby type 1 3rd order 300-2700Hz fs=8000 - m_filterStage1 = new CIIRDirectForm1Filter(0.29495028f, 0.0f, -0.29495028f, 1.0f, -0.61384624f, -0.057158668f, FILTER_GAIN_DB); - m_filterStage2 = new CIIRDirectForm1Filter(1.0f, 2.0f, 1.0f, 1.0f, 0.9946123f, 0.6050482f, FILTER_GAIN_DB); - m_filterStage3 = new CIIRDirectForm1Filter(1.0f, -2.0f, 1.0f, 1.0f, -1.8414584f, 0.8804949f, FILTER_GAIN_DB); -} - -CFMControl::~CFMControl() -{ - delete m_preemphasis ; - delete m_deemphasis ; - - delete m_filterStage1; - delete m_filterStage2; - delete m_filterStage3; -} - -bool CFMControl::writeModem(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - if (m_network == NULL) - return true; - - if (data[0U] == TAG_HEADER) - return true; - - if (data[0U] == TAG_EOT) - return m_network->writeEOT(); - - if (data[0U] != TAG_DATA) - return false; - - m_incomingRFAudio.addData(data + 1U, length - 1U); - unsigned int bufferLength = m_incomingRFAudio.dataSize(); - if (bufferLength > 252U)//168 samples 12-bit - bufferLength = 252U; - - if (bufferLength >= 3U) { - bufferLength = bufferLength - bufferLength % 3U; //round down to nearest multiple of 3 - unsigned char bufferData[252U]; - m_incomingRFAudio.getData(bufferData, bufferLength); - - unsigned int pack = 0U; - unsigned char* packPointer = (unsigned char*)&pack; - float out[168U]; - unsigned int nOut = 0U; - short unpackedSamples[2U]; - - for (unsigned int i = 0U; i < bufferLength; i += 3U) { - //extract unsigned 12 bit unsigned sample pairs packed into 3 bytes to 16 bit signed - packPointer[0U] = bufferData[i]; - packPointer[1U] = bufferData[i + 1U]; - packPointer[2U] = bufferData[i + 2U]; - unpackedSamples[1U] = short(int(pack & FM_MASK) - 2048); - unpackedSamples[0U] = short(int(pack >> 12) - 2048); - - //process unpacked sample pair - for (unsigned char j = 0U; j < 2U; j++) { - // Convert to float (-1.0 to +1.0) - float sampleFloat = float(unpackedSamples[j]) / 2048.0F; - - // De-emphasise and remove CTCSS - sampleFloat = m_deemphasis->filter(sampleFloat); - out[nOut++] = m_filterStage3->filter(m_filterStage2->filter(m_filterStage1->filter(sampleFloat))); - } - } - -#if defined(DUMP_RF_AUDIO) - FILE * audiofile = fopen("./audiodump.bin", "ab"); - if(audiofile != NULL) { - fwrite(out, sizeof(float), nOut, audiofile); - fclose(audiofile); - } -#endif - return m_network->writeData(out, nOut); - } - - return true; -} - -unsigned int CFMControl::readModem(unsigned char* data, unsigned int space) -{ - assert(data != NULL); - assert(space > 0U); - - if (m_network == NULL) - return 0U; - - if (space > 252U) - space = 252U; - - float netData[168U]; // Modem can handle up to 168 samples at a time - unsigned int length = m_network->read(netData, 168U); - if (length == 0U) - return 0U; - - unsigned int pack = 0U; - unsigned char* packPointer = (unsigned char*)&pack; - unsigned int nData = 0U; - - for (unsigned int i = 0; i < length; i++) { - // Pre-emphasis - float sampleFloat = m_preemphasis->filter(netData[i]); - - // Convert float to 12-bit samples (0 to 4095) - unsigned int sample12bit = (unsigned int)((sampleFloat + 1.0F) * 2048.0F + 0.5F); - - // pack 2 samples onto 3 bytes - if((i & 1U) == 0) { - pack = 0U; - pack = sample12bit << 12; - } else { - pack |= sample12bit; - - data[nData++] = packPointer[0U]; - data[nData++] = packPointer[1U]; - data[nData++] = packPointer[2U]; - } - } - - return nData; -} - -void CFMControl::clock(unsigned int ms) -{ - // May not be needed -} - -void CFMControl::enable(bool enabled) -{ - // May not be needed -} diff --git a/FMControl.h b/FMControl.h deleted file mode 100644 index ac2142e..0000000 --- a/FMControl.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 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(FMControl_H) -#define FMControl_H - -#include "FMNetwork.h" -#include "Defines.h" -#include "IIRDirectForm1Filter.h" - -// Uncomment this to dump audio to a raw audio file -// The file will be written in same folder as executable -// Toplay the file : ffplay -autoexit -f u16be -ar 8000 audiodump.bin -// #define DUMP_RF_AUDIO - -class CFMControl { -public: - CFMControl(CFMNetwork* network); - ~CFMControl(); - - bool writeModem(const unsigned char* data, unsigned int length); - - unsigned int readModem(unsigned char* data, unsigned int space); - - void clock(unsigned int ms); - - void enable(bool enabled); - -private: - CFMNetwork* m_network; - bool m_enabled; - CRingBuffer m_incomingRFAudio; - CIIRDirectForm1Filter* m_preemphasis; - CIIRDirectForm1Filter* m_deemphasis; - CIIRDirectForm1Filter* m_filterStage1; - CIIRDirectForm1Filter* m_filterStage2; - CIIRDirectForm1Filter* m_filterStage3; -}; - -#endif diff --git a/FMNetwork.cpp b/FMNetwork.cpp deleted file mode 100644 index bf76ccb..0000000 --- a/FMNetwork.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 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 "FMNetwork.h" -#include "Defines.h" -#include "Utils.h" -#include "Log.h" - -#include -#include -#include - -const unsigned int BUFFER_LENGTH = 500U; - -CFMNetwork::CFMNetwork(const std::string& localAddress, unsigned int localPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug) : -m_socket(localAddress, localPort), -m_addr(), -m_addrLen(0U), -m_sampleRate(sampleRate), -m_debug(debug), -m_enabled(false), -m_buffer(2000U, "FM Network"), -m_pollTimer(1000U, 5U) -{ - assert(gatewayPort > 0U); - assert(!gatewayAddress.empty()); - assert(sampleRate > 0U); - - if (CUDPSocket::lookup(gatewayAddress, gatewayPort, m_addr, m_addrLen) != 0) - m_addrLen = 0U; - -#if !defined(_WIN32) && !defined(_WIN64) - int error; - m_incoming = ::src_new(SRC_SINC_FASTEST, 1, &error); - m_outgoing = ::src_new(SRC_SINC_FASTEST, 1, &error); - - assert(m_incoming != NULL); - assert(m_outgoing != NULL); -#endif -} - -CFMNetwork::~CFMNetwork() -{ -#if !defined(_WIN32) && !defined(_WIN64) - ::src_delete(m_incoming); - ::src_delete(m_outgoing); -#endif -} - -bool CFMNetwork::open() -{ - if (m_addrLen == 0U) { - LogError("Unable to resolve the address of the FM Gateway"); - return false; - } - - LogMessage("Opening FM network connection"); - - m_pollTimer.start(); - - return m_socket.open(m_addr); -} - -bool CFMNetwork::writeData(float* data, unsigned int nSamples) -{ - assert(data != NULL); - assert(nSamples > 0U); - -#if !defined(_WIN32) && !defined(_WIN64) - assert(m_outgoing != NULL); - - float out[1000U]; - SRC_DATA src; - - if (m_sampleRate != 8000U) { - src.data_in = data; - src.data_out = out; - src.input_frames = nSamples; - src.output_frames = 1000; - src.end_of_input = 0; - src.src_ratio = double(m_sampleRate) / 8000.0; - - int ret = ::src_process(m_outgoing, &src); - if (ret != 0) { - LogError("Error up/downsampling of the output audio has an error - %s", src_strerror(ret)); - return false; - } - } else { - src.data_out = data; - src.output_frames_gen = nSamples; - } -#endif - - unsigned int length = 3U; - - unsigned char buffer[1500U]; - ::memset(buffer, 0x00U, 1500U); - - buffer[0U] = 'F'; - buffer[1U] = 'M'; - buffer[2U] = 'D'; - -#if defined(_WIN32) || defined(_WIN64) - for (long i = 0L; i < nSamples; i++) { - unsigned short val = (unsigned short)((data[i] + 1.0F) * 32767.0F + 0.5F); -#else - for (long i = 0L; i < src.output_frames_gen; i++) { - unsigned short val = (unsigned short)((src.data_out[i] + 1.0F) * 32767.0F + 0.5F); -#endif - - buffer[length++] = (val >> 8) & 0xFFU; - buffer[length++] = (val >> 0) & 0xFFU; - } - - if (m_debug) - CUtils::dump(1U, "FM Network Data Sent", buffer, length); - - return m_socket.write(buffer, length, m_addr, m_addrLen); -} - -bool CFMNetwork::writeEOT() -{ - unsigned char buffer[10U]; - ::memset(buffer, 0x00U, 10U); - - buffer[0U] = 'F'; - buffer[1U] = 'M'; - buffer[2U] = 'E'; - - if (m_debug) - CUtils::dump(1U, "FM Network End of Transmission Sent", buffer, 3U); - - return m_socket.write(buffer, 3U, m_addr, m_addrLen); -} - -void CFMNetwork::clock(unsigned int ms) -{ - m_pollTimer.clock(ms); - if (m_pollTimer.hasExpired()) { - writePoll(); - m_pollTimer.start(); - } - - unsigned char buffer[BUFFER_LENGTH]; - - sockaddr_storage addr; - unsigned int addrlen; - int length = m_socket.read(buffer, BUFFER_LENGTH, addr, addrlen); - if (length <= 0) - return; - - // Check if the data is for us - if (!CUDPSocket::match(addr, m_addr)) { - LogMessage("FM packet received from an invalid source"); - return; - } - - // Ignore incoming polls - if (::memcmp(buffer, "FMP", 3U) == 0) - return; - - // Invalid packet type? - if (::memcmp(buffer, "FMD", 3U) != 0) - return; - - if (!m_enabled) - return; - - if (m_debug) - CUtils::dump(1U, "FM Network Data Received", buffer, length); - - m_buffer.addData(buffer + 3U, length - 3U); -} - -unsigned int CFMNetwork::read(float* data, unsigned int nSamples) -{ - assert(data != NULL); - assert(nSamples > 0U); - - unsigned int bytes = m_buffer.dataSize() / sizeof(unsigned short); - if (bytes == 0U) - return 0U; - - if (bytes < nSamples) - nSamples = bytes; - - unsigned char buffer[1500U]; - m_buffer.getData(buffer, nSamples * sizeof(unsigned short)); - -#if !defined(_WIN32) && !defined(_WIN64) - assert(m_incoming != NULL); - - SRC_DATA src; - - if (m_sampleRate != 8000U) { - float in[750U]; - - for (unsigned int i = 0U; i < nSamples; i++) { - unsigned short val = ((buffer[i * 2U + 0U] & 0xFFU) << 8) + - ((buffer[i * 2U + 1U] & 0xFFU) << 0); - in[i] = (float(val) - 32768.0F) / 32768.0F; - } - - src.data_in = in; - src.data_out = data; - src.input_frames = nSamples; - src.output_frames = 750; - src.end_of_input = 0; - src.src_ratio = 8000.0 / double(m_sampleRate); - - int ret = ::src_process(m_incoming, &src); - if (ret != 0) { - LogError("Error up/downsampling of the input audio has an error - %s", src_strerror(ret)); - return 0U; - } - - return src.output_frames_gen; - } else { -#endif - for (unsigned int i = 0U; i < nSamples; i++) { - unsigned short val = ((buffer[i * 2U + 0U] & 0xFFU) << 8) + - ((buffer[i * 2U + 1U] & 0xFFU) << 0); - data[i] = (float(val) - 32768.0F) / 32768.0F; - } - - return nSamples; -#if !defined(_WIN32) && !defined(_WIN64) - } -#endif -} - -void CFMNetwork::reset() -{ -#if !defined(_WIN32) && !defined(_WIN64) - assert(m_incoming != NULL); - assert(m_outgoing != NULL); - - ::src_reset(m_incoming); - ::src_reset(m_outgoing); -#endif - - m_buffer.clear(); -} - -void CFMNetwork::close() -{ - m_socket.close(); - - LogMessage("Closing FM network connection"); -} - -void CFMNetwork::enable(bool enabled) -{ - if (enabled && !m_enabled) - reset(); - else if (!enabled && m_enabled) - reset(); - - m_enabled = enabled; -} - -bool CFMNetwork::writePoll() -{ - unsigned char buffer[3U]; - - buffer[0U] = 'F'; - buffer[1U] = 'M'; - buffer[2U] = 'P'; - - if (m_debug) - CUtils::dump(1U, "FM Network Poll Sent", buffer, 3U); - - return m_socket.write(buffer, 3U, m_addr, m_addrLen); -} diff --git a/FMNetwork.h b/FMNetwork.h deleted file mode 100644 index aa99751..0000000 --- a/FMNetwork.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 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 FMNetwork_H -#define FMNetwork_H - -#include "RingBuffer.h" -#include "UDPSocket.h" -#include "Timer.h" - -#if !defined(_WIN32) && !defined(_WIN64) -#include -#endif - -#include -#include - -class CFMNetwork { -public: - CFMNetwork(const std::string& myAddress, unsigned int myPort, const std::string& gatewayAddress, unsigned int gatewayPort, unsigned int sampleRate, bool debug); - ~CFMNetwork(); - - bool open(); - - void enable(bool enabled); - - bool writeData(float* data, unsigned int nSamples); - - bool writeEOT(); - - unsigned int read(float* data, unsigned int nSamples); - - void reset(); - - void close(); - - void clock(unsigned int ms); - -private: - CUDPSocket m_socket; - sockaddr_storage m_addr; - unsigned int m_addrLen; - unsigned int m_sampleRate; - bool m_debug; - bool m_enabled; - CRingBuffer m_buffer; - CTimer m_pollTimer; -#if !defined(_WIN32) && !defined(_WIN64) - SRC_STATE* m_incoming; - SRC_STATE* m_outgoing; -#endif - - bool writePoll(); -}; - -#endif diff --git a/I2CController.cpp b/I2CController.cpp index 9886443..247129c 100644 --- a/I2CController.cpp +++ b/I2CController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2002-2004,2007-2011,2013,2014-2017,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2002-2004,2007-2011,2013,2014-2017 by Jonathan Naylor G4KLX * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,6 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#if defined(__linux__) - #include "I2CController.h" #include "Log.h" @@ -26,18 +24,52 @@ #include #include + +#if defined(_WIN32) || defined(_WIN64) + +#include +#include + +CI2CController::CI2CController(const std::string& device, SERIAL_SPEED speed, unsigned int address, bool assertRTS) : +CSerialController(device, speed, assertRTS), +m_address(address) +{ +} + +CI2CController::~CI2CController() +{ +} + +bool CI2CController::open() +{ + return CSerialController::open(); +} + +int CI2CController::read(unsigned char* buffer, unsigned int length) +{ + return CSerialController::read(buffer, length); +} + +int CI2CController::write(const unsigned char* buffer, unsigned int length) +{ + return CSerialController::write(buffer, length); +} + +#else + #include #include #include #include #include #include +#if defined(__linux__) #include +#endif -CI2CController::CI2CController(const std::string& device, unsigned int address) : -m_device(device), -m_address(address), -m_fd(-1) +CI2CController::CI2CController(const std::string& device, SERIAL_SPEED speed, unsigned int address, bool assertRTS) : +CSerialController(device, speed, assertRTS), +m_address(address) { } @@ -49,6 +81,7 @@ bool CI2CController::open() { assert(m_fd == -1); +#if defined(__linux__) m_fd = ::open(m_device.c_str(), O_RDWR); if (m_fd < 0) { LogError("Cannot open device - %s", m_device.c_str()); @@ -56,16 +89,19 @@ bool CI2CController::open() } if (::ioctl(m_fd, I2C_TENBIT, 0) < 0) { - LogError("I2C: failed to set 7bitaddress"); + LogError("CI2C: failed to set 7bitaddress"); ::close(m_fd); return false; } if (::ioctl(m_fd, I2C_SLAVE, m_address) < 0) { - LogError("I2C: Failed to acquire bus access/talk to slave 0x%02X", m_address); + LogError("CI2C: Failed to acquire bus access/talk to slave 0x%02X", m_address); ::close(m_fd); return false; } +#else + #warning "I2C controller supports Linux only" +#endif return true; } @@ -81,6 +117,7 @@ int CI2CController::read(unsigned char* buffer, unsigned int length) unsigned int offset = 0U; while (offset < length) { +#if defined(__linux__) ssize_t n = ::read(m_fd, buffer + offset, 1U); if (n < 0) { if (errno != EAGAIN) { @@ -91,6 +128,7 @@ int CI2CController::read(unsigned char* buffer, unsigned int length) if (n > 0) offset += n; +#endif } return length; @@ -106,7 +144,10 @@ int CI2CController::write(const unsigned char* buffer, unsigned int length) unsigned int ptr = 0U; while (ptr < length) { - ssize_t n = ::write(m_fd, buffer + ptr, 1U); + ssize_t n = 0U; +#if defined(__linux__) + n = ::write(m_fd, buffer + ptr, 1U); +#endif if (n < 0) { if (errno != EAGAIN) { LogError("Error returned from write(), errno=%d", errno); @@ -121,12 +162,4 @@ int CI2CController::write(const unsigned char* buffer, unsigned int length) return length; } -void CI2CController::close() -{ - assert(m_fd != -1); - - ::close(m_fd); - m_fd = -1; -} - #endif diff --git a/I2CController.h b/I2CController.h index 281c587..6e59672 100644 --- a/I2CController.h +++ b/I2CController.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2002-2004,2007-2009,2011-2013,2015-2017,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2002-2004,2007-2009,2011-2013,2015-2017 by Jonathan Naylor G4KLX * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX * * This program is free software; you can redistribute it and/or modify @@ -20,15 +20,11 @@ #ifndef I2CController_H #define I2CController_H -#if defined(__linux__) +#include "SerialController.h" -#include "SerialPort.h" - -#include - -class CI2CController : public ISerialPort { +class CI2CController : public CSerialController { public: - CI2CController(const std::string& device, unsigned int address = 0x22U); + CI2CController(const std::string& device, SERIAL_SPEED speed, unsigned int address = 0x22U, bool assertRTS = false); virtual ~CI2CController(); virtual bool open(); @@ -37,14 +33,8 @@ public: virtual int write(const unsigned char* buffer, unsigned int length); - virtual void close(); - private: - std::string m_device; unsigned int m_address; - int m_fd; }; #endif - -#endif diff --git a/IIRDirectForm1Filter.cpp b/IIRDirectForm1Filter.cpp deleted file mode 100644 index 946acdd..0000000 --- a/IIRDirectForm1Filter.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2015-2020 by Jonathan Naylor G4KLX - * Copyright (C) 2020 by Geoffrey Merck - F4FXL KC3FRA - * - * 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 "IIRDirectForm1Filter.h" -#include "math.h" - -CIIRDirectForm1Filter::CIIRDirectForm1Filter(float b0, float b1, float b2, float , float a1, float a2, float addtionalGaindB) : -m_x2(0.0F), -m_y2(0.0F), -m_x1(0.0F), -m_y1(0.0F), -m_b0(b0), -m_b1(b1), -m_b2(b2), -m_a1(a1), -m_a2(a2), -m_additionalGainLin(0.0F) -{ - m_additionalGainLin = ::powf(10.0F, addtionalGaindB / 20.0F); -} - -float CIIRDirectForm1Filter::filter(float sample) -{ - float output = m_b0 * sample - + m_b1 * m_x1 - + m_b2 * m_x2 - - m_a1 * m_y1 - - m_a2 * m_y2; - - m_x2 = m_x1; - m_y2 = m_y1; - m_x1 = sample; - m_y1 = output; - - return output * m_additionalGainLin; -} - -void CIIRDirectForm1Filter::reset() -{ - m_x1 = 0.0f; - m_x2 = 0.0f; - m_y1 = 0.0f; - m_y2 = 0.0f; -} diff --git a/IIRDirectForm1Filter.h b/IIRDirectForm1Filter.h deleted file mode 100644 index f575f7f..0000000 --- a/IIRDirectForm1Filter.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2015-2020 by Jonathan Naylor G4KLX - * Copyright (C) 2020 by Geoffrey Merck - F4FXL KC3FRA - * - * 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(IIRDIRECTFORM1FILTER_H) -#define IIRDIRECTFORM1FILTER_H - -class CIIRDirectForm1Filter -{ -public: - CIIRDirectForm1Filter(float b0, float b1, float b2, float, float a1, float a2, float additionalGaindB); - float filter(float sample); - void reset(); - -private: -// delay line - float m_x2; // x[n-2] - float m_y2; // y[n-2] - float m_x1; // x[n-1] - float m_y1; // y[n-1] - - // coefficients - // FIR - float m_b0; - float m_b1; - float m_b2; - // IIR - float m_a1; - float m_a2; - - float m_additionalGainLin; -}; - - -#endif \ No newline at end of file diff --git a/ISSUES.txt b/ISSUES.txt new file mode 100644 index 0000000..537f79f --- /dev/null +++ b/ISSUES.txt @@ -0,0 +1,9 @@ +D-Star: On some radios, the header is not decoded correctly. It looks like frequency drift at the beginning of the transmission. + +DMR: DMO mode doesn't wake up older radios like other radios do. + +YSF: No known issues. + +P25: Upgrade the filters, processing power in the Due permiting. + +NXDN: No known issues. diff --git a/Images/M17.bmp b/Images/M17.bmp deleted file mode 100644 index 8d88e8e0f05499dbd1193a3a0b6f7d471dbc18e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58014 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 diff --git a/LCDproc.cpp b/LCDproc.cpp index 0ef66eb..19ac15e 100644 --- a/LCDproc.cpp +++ b/LCDproc.cpp @@ -95,7 +95,6 @@ const unsigned int DMR_RSSI_COUNT = 4U; // 4 * 360ms = 1440ms const unsigned int YSF_RSSI_COUNT = 13U; // 13 * 100ms = 1300ms const unsigned int P25_RSSI_COUNT = 7U; // 7 * 180ms = 1260ms const unsigned int NXDN_RSSI_COUNT = 28U; // 28 * 40ms = 1120ms -const unsigned int M17_RSSI_COUNT = 28U; // 28 * 40ms = 1120ms CLCDproc::CLCDproc(std::string address, unsigned int port, unsigned int localPort, const std::string& callsign, unsigned int dmrid, bool displayClock, bool utc, bool duplex, bool dimOnIdle) : CDisplay(), @@ -189,7 +188,6 @@ void CLCDproc::setIdleInt() socketPrintf(m_socketfd, "screen_set YSF -priority hidden"); socketPrintf(m_socketfd, "screen_set P25 -priority hidden"); socketPrintf(m_socketfd, "screen_set NXDN -priority hidden"); - socketPrintf(m_socketfd, "screen_set M17 -priority hidden"); socketPrintf(m_socketfd, "widget_set Status Status %u %u Idle", m_cols - 3, m_rows); socketPrintf(m_socketfd, "output 0"); // Clear all LEDs } @@ -209,7 +207,6 @@ void CLCDproc::setErrorInt(const char* text) socketPrintf(m_socketfd, "screen_set YSF -priority hidden"); socketPrintf(m_socketfd, "screen_set P25 -priority hidden"); socketPrintf(m_socketfd, "screen_set NXDN -priority hidden"); - socketPrintf(m_socketfd, "screen_set M17 -priority hidden"); socketPrintf(m_socketfd, "widget_set Status Status %u %u Error", m_cols - 4, m_rows); socketPrintf(m_socketfd, "output 0"); // Clear all LEDs } @@ -227,7 +224,6 @@ void CLCDproc::setLockoutInt() socketPrintf(m_socketfd, "screen_set YSF -priority hidden"); socketPrintf(m_socketfd, "screen_set P25 -priority hidden"); socketPrintf(m_socketfd, "screen_set NXDN -priority hidden"); - socketPrintf(m_socketfd, "screen_set M17 -priority hidden"); socketPrintf(m_socketfd, "widget_set Status Status %u %u Lockout", m_cols - 6, m_rows); socketPrintf(m_socketfd, "output 0"); // Clear all LEDs } @@ -247,7 +243,6 @@ void CLCDproc::setQuitInt() socketPrintf(m_socketfd, "screen_set YSF -priority hidden"); socketPrintf(m_socketfd, "screen_set P25 -priority hidden"); socketPrintf(m_socketfd, "screen_set NXDN -priority hidden"); - socketPrintf(m_socketfd, "screen_set M17 -priority hidden"); socketPrintf(m_socketfd, "widget_set Status Status %u %u Stopped", m_cols - 6, m_rows); socketPrintf(m_socketfd, "output 0"); // Clear all LEDs } @@ -265,7 +260,6 @@ void CLCDproc::setFMInt() socketPrintf(m_socketfd, "screen_set YSF -priority hidden"); socketPrintf(m_socketfd, "screen_set P25 -priority hidden"); socketPrintf(m_socketfd, "screen_set NXDN -priority hidden"); - socketPrintf(m_socketfd, "screen_set M17 -priority hidden"); socketPrintf(m_socketfd, "widget_set Status Status %u %u FM", m_cols - 6, m_rows); socketPrintf(m_socketfd, "output 0"); // Clear all LEDs } @@ -562,51 +556,6 @@ void CLCDproc::clearNXDNInt() socketPrintf(m_socketfd, "output 16"); // Set LED5 color green } -void CLCDproc::writeM17Int(const char* source, const char* dest, const char* type) -{ - assert(source != NULL); - assert(dest != NULL); - assert(type != NULL); - - m_clockDisplayTimer.stop(); // Stop the clock display - - socketPrintf(m_socketfd, "screen_set M17 -priority foreground"); - socketPrintf(m_socketfd, "widget_set M17 Mode 1 1 M17"); - - if (m_rows == 2U) { - socketPrintf(m_socketfd, "widget_set M17 Line2 1 2 15 2 h 3 \"%.9s > %.9s\"", source, dest); - } - else { - socketPrintf(m_socketfd, "widget_set M17 Line2 1 2 15 2 h 3 \"%.9s >\"", source); - socketPrintf(m_socketfd, "widget_set M17 Line3 1 3 15 3 h 3 \"%.9ss\"", dest); - socketPrintf(m_socketfd, "output 255"); // Set LED5 color red - } - - m_dmr = false; - m_rssiCount1 = 0U; -} - -void CLCDproc::writeM17RSSIInt(unsigned char rssi) -{ - if (m_rssiCount1 == 0U) { - socketPrintf(m_socketfd, "widget_set M17 Line4 1 4 %u 4 h 3 \"-%3udBm\"", m_cols - 1, rssi); - } - - m_rssiCount1++; - if (m_rssiCount1 >= M17_RSSI_COUNT) - m_rssiCount1 = 0U; -} - -void CLCDproc::clearM17Int() -{ - m_clockDisplayTimer.stop(); // Stop the clock display - - socketPrintf(m_socketfd, "widget_set M17 Line2 1 2 15 2 h 3 \"Listening\""); - socketPrintf(m_socketfd, "widget_set M17 Line3 1 3 15 3 h 3 \"\""); - socketPrintf(m_socketfd, "widget_set M17 Line4 1 4 15 4 h 3 \"\""); - socketPrintf(m_socketfd, "output 16"); // Set LED5 color green -} - void CLCDproc::writePOCSAGInt(uint32_t ric, const std::string& message) { } @@ -901,21 +850,5 @@ void CLCDproc::defineScreens() socketPrintf(m_socketfd, "widget_set NXDN Line4 4 2 15 2 h 3 \" \""); */ -// The M17 Screen - - socketPrintf(m_socketfd, "screen_add M17"); - socketPrintf(m_socketfd, "screen_set M17 -name M17 -heartbeat on -priority hidden -backlight on"); - - socketPrintf(m_socketfd, "widget_add M17 Mode string"); - socketPrintf(m_socketfd, "widget_add M17 Line2 scroller"); - socketPrintf(m_socketfd, "widget_add M17 Line3 scroller"); - socketPrintf(m_socketfd, "widget_add M17 Line4 scroller"); - - /* Do we need to pre-populate the values?? - socketPrintf(m_socketfd, "widget_set M17 Line3 2 1 15 1 h 3 \"Listening\""); - socketPrintf(m_socketfd, "widget_set M17 Line3 3 1 15 1 h 3 \" \""); - socketPrintf(m_socketfd, "widget_set M17 Line4 4 2 15 2 h 3 \" \""); - */ - m_screensDefined = true; } diff --git a/LCDproc.h b/LCDproc.h index 553968a..896e8a6 100644 --- a/LCDproc.h +++ b/LCDproc.h @@ -62,10 +62,6 @@ protected: virtual void writeNXDNRSSIInt(unsigned char rssi); virtual void clearNXDNInt(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void writeM17RSSIInt(unsigned char rssi); - virtual void clearM17Int(); - virtual void writePOCSAGInt(uint32_t ric, const std::string& message); virtual void clearPOCSAGInt(); diff --git a/M17CRC.cpp b/M17CRC.cpp deleted file mode 100644 index 7ba4da6..0000000 --- a/M17CRC.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 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. - */ - -#include "M17CRC.h" - -#include -#include - -const uint16_t CRC_TABLE[] = {0x0000U, 0x5935U, 0xB26AU, 0xEB5FU, 0x3DE1U, 0x64D4U, 0x8F8BU, 0xD6BEU, 0x7BC2U, 0x22F7U, 0xC9A8U, - 0x909DU, 0x4623U, 0x1F16U, 0xF449U, 0xAD7CU, 0xF784U, 0xAEB1U, 0x45EEU, 0x1CDBU, 0xCA65U, 0x9350U, - 0x780FU, 0x213AU, 0x8C46U, 0xD573U, 0x3E2CU, 0x6719U, 0xB1A7U, 0xE892U, 0x03CDU, 0x5AF8U, 0xB63DU, - 0xEF08U, 0x0457U, 0x5D62U, 0x8BDCU, 0xD2E9U, 0x39B6U, 0x6083U, 0xCDFFU, 0x94CAU, 0x7F95U, 0x26A0U, - 0xF01EU, 0xA92BU, 0x4274U, 0x1B41U, 0x41B9U, 0x188CU, 0xF3D3U, 0xAAE6U, 0x7C58U, 0x256DU, 0xCE32U, - 0x9707U, 0x3A7BU, 0x634EU, 0x8811U, 0xD124U, 0x079AU, 0x5EAFU, 0xB5F0U, 0xECC5U, 0x354FU, 0x6C7AU, - 0x8725U, 0xDE10U, 0x08AEU, 0x519BU, 0xBAC4U, 0xE3F1U, 0x4E8DU, 0x17B8U, 0xFCE7U, 0xA5D2U, 0x736CU, - 0x2A59U, 0xC106U, 0x9833U, 0xC2CBU, 0x9BFEU, 0x70A1U, 0x2994U, 0xFF2AU, 0xA61FU, 0x4D40U, 0x1475U, - 0xB909U, 0xE03CU, 0x0B63U, 0x5256U, 0x84E8U, 0xDDDDU, 0x3682U, 0x6FB7U, 0x8372U, 0xDA47U, 0x3118U, - 0x682DU, 0xBE93U, 0xE7A6U, 0x0CF9U, 0x55CCU, 0xF8B0U, 0xA185U, 0x4ADAU, 0x13EFU, 0xC551U, 0x9C64U, - 0x773BU, 0x2E0EU, 0x74F6U, 0x2DC3U, 0xC69CU, 0x9FA9U, 0x4917U, 0x1022U, 0xFB7DU, 0xA248U, 0x0F34U, - 0x5601U, 0xBD5EU, 0xE46BU, 0x32D5U, 0x6BE0U, 0x80BFU, 0xD98AU, 0x6A9EU, 0x33ABU, 0xD8F4U, 0x81C1U, - 0x577FU, 0x0E4AU, 0xE515U, 0xBC20U, 0x115CU, 0x4869U, 0xA336U, 0xFA03U, 0x2CBDU, 0x7588U, 0x9ED7U, - 0xC7E2U, 0x9D1AU, 0xC42FU, 0x2F70U, 0x7645U, 0xA0FBU, 0xF9CEU, 0x1291U, 0x4BA4U, 0xE6D8U, 0xBFEDU, - 0x54B2U, 0x0D87U, 0xDB39U, 0x820CU, 0x6953U, 0x3066U, 0xDCA3U, 0x8596U, 0x6EC9U, 0x37FCU, 0xE142U, - 0xB877U, 0x5328U, 0x0A1DU, 0xA761U, 0xFE54U, 0x150BU, 0x4C3EU, 0x9A80U, 0xC3B5U, 0x28EAU, 0x71DFU, - 0x2B27U, 0x7212U, 0x994DU, 0xC078U, 0x16C6U, 0x4FF3U, 0xA4ACU, 0xFD99U, 0x50E5U, 0x09D0U, 0xE28FU, - 0xBBBAU, 0x6D04U, 0x3431U, 0xDF6EU, 0x865BU, 0x5FD1U, 0x06E4U, 0xEDBBU, 0xB48EU, 0x6230U, 0x3B05U, - 0xD05AU, 0x896FU, 0x2413U, 0x7D26U, 0x9679U, 0xCF4CU, 0x19F2U, 0x40C7U, 0xAB98U, 0xF2ADU, 0xA855U, - 0xF160U, 0x1A3FU, 0x430AU, 0x95B4U, 0xCC81U, 0x27DEU, 0x7EEBU, 0xD397U, 0x8AA2U, 0x61FDU, 0x38C8U, - 0xEE76U, 0xB743U, 0x5C1CU, 0x0529U, 0xE9ECU, 0xB0D9U, 0x5B86U, 0x02B3U, 0xD40DU, 0x8D38U, 0x6667U, - 0x3F52U, 0x922EU, 0xCB1BU, 0x2044U, 0x7971U, 0xAFCFU, 0xF6FAU, 0x1DA5U, 0x4490U, 0x1E68U, 0x475DU, - 0xAC02U, 0xF537U, 0x2389U, 0x7ABCU, 0x91E3U, 0xC8D6U, 0x65AAU, 0x3C9FU, 0xD7C0U, 0x8EF5U, 0x584BU, - 0x017EU, 0xEA21U, 0xB314U}; - -bool CM17CRC::checkCRC(const unsigned char* in, unsigned int nBytes) -{ - assert(in != NULL); - assert(nBytes > 2U); - - uint16_t crc = createCRC(in, nBytes - 2U); - - uint8_t temp[2U]; - temp[0U] = (crc >> 8) & 0xFFU; - temp[1U] = (crc >> 0) & 0xFFU; - - return temp[0U] == in[nBytes - 2U] && temp[1U] == in[nBytes - 1U]; -} - -void CM17CRC::encodeCRC(unsigned char* in, unsigned int nBytes) -{ - assert(in != NULL); - assert(nBytes > 2U); - - uint16_t crc = createCRC(in, nBytes - 2U); - - in[nBytes - 2U] = (crc >> 8) & 0xFFU; - in[nBytes - 1U] = (crc >> 0) & 0xFFU; -} - -uint16_t CM17CRC::createCRC(const unsigned char* in, unsigned int nBytes) -{ - assert(in != NULL); - - uint16_t crc = 0xFFFFU; - - for (unsigned int i = 0U; i < nBytes; i++) - crc = (crc << 8) ^ CRC_TABLE[((crc >> 8) ^ uint16_t(in[i])) & 0x00FFU]; - - return crc; -} - diff --git a/M17CRC.h b/M17CRC.h deleted file mode 100644 index 22c8f31..0000000 --- a/M17CRC.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 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(M17CRC_H) -#define M17CRC_H - -#include - -class CM17CRC -{ -public: - static bool checkCRC(const unsigned char* in, unsigned int nBytes); - static void encodeCRC(unsigned char* in, unsigned int nBytes); - -private: - static uint16_t createCRC(const unsigned char* in, unsigned int nBytes); -}; - -#endif diff --git a/M17Control.cpp b/M17Control.cpp deleted file mode 100644 index 9be8607..0000000 --- a/M17Control.cpp +++ /dev/null @@ -1,802 +0,0 @@ -/* - * 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 "M17Convolution.h" -#include "M17Utils.h" -#include "M17CRC.h" -#include "Golay24128.h" -#include "Utils.h" -#include "Sync.h" -#include "Log.h" - -#include -#include -#include -#include - -const unsigned int INTERLEAVER[] = { - 0U, 137U, 90U, 227U, 180U, 317U, 270U, 39U, 360U, 129U, 82U, 219U, 172U, 309U, 262U, 31U, 352U, 121U, 74U, 211U, 164U, - 301U, 254U, 23U, 344U, 113U, 66U, 203U, 156U, 293U, 246U, 15U, 336U, 105U, 58U, 195U, 148U, 285U, 238U, 7U, 328U, 97U, - 50U, 187U, 140U, 277U, 230U, 367U, 320U, 89U, 42U, 179U, 132U, 269U, 222U, 359U, 312U, 81U, 34U, 171U, 124U, 261U, 214U, - 351U, 304U, 73U, 26U, 163U, 116U, 253U, 206U, 343U, 296U, 65U, 18U, 155U, 108U, 245U, 198U, 335U, 288U, 57U, 10U, 147U, - 100U, 237U, 190U, 327U, 280U, 49U, 2U, 139U, 92U, 229U, 182U, 319U, 272U, 41U, 362U, 131U, 84U, 221U, 174U, 311U, 264U, - 33U, 354U, 123U, 76U, 213U, 166U, 303U, 256U, 25U, 346U, 115U, 68U, 205U, 158U, 295U, 248U, 17U, 338U, 107U, 60U, 197U, - 150U, 287U, 240U, 9U, 330U, 99U, 52U, 189U, 142U, 279U, 232U, 1U, 322U, 91U, 44U, 181U, 134U, 271U, 224U, 361U, 314U, 83U, - 36U, 173U, 126U, 263U, 216U, 353U, 306U, 75U, 28U, 165U, 118U, 255U, 208U, 345U, 298U, 67U, 20U, 157U, 110U, 247U, 200U, - 337U, 290U, 59U, 12U, 149U, 102U, 239U, 192U, 329U, 282U, 51U, 4U, 141U, 94U, 231U, 184U, 321U, 274U, 43U, 364U, 133U, 86U, - 223U, 176U, 313U, 266U, 35U, 356U, 125U, 78U, 215U, 168U, 305U, 258U, 27U, 348U, 117U, 70U, 207U, 160U, 297U, 250U, 19U, - 340U, 109U, 62U, 199U, 152U, 289U, 242U, 11U, 332U, 101U, 54U, 191U, 144U, 281U, 234U, 3U, 324U, 93U, 46U, 183U, 136U, 273U, - 226U, 363U, 316U, 85U, 38U, 175U, 128U, 265U, 218U, 355U, 308U, 77U, 30U, 167U, 120U, 257U, 210U, 347U, 300U, 69U, 22U, - 159U, 112U, 249U, 202U, 339U, 292U, 61U, 14U, 151U, 104U, 241U, 194U, 331U, 284U, 53U, 6U, 143U, 96U, 233U, 186U, 323U, - 276U, 45U, 366U, 135U, 88U, 225U, 178U, 315U, 268U, 37U, 358U, 127U, 80U, 217U, 170U, 307U, 260U, 29U, 350U, 119U, 72U, - 209U, 162U, 299U, 252U, 21U, 342U, 111U, 64U, 201U, 154U, 291U, 244U, 13U, 334U, 103U, 56U, 193U, 146U, 283U, 236U, 5U, - 326U, 95U, 48U, 185U, 138U, 275U, 228U, 365U, 318U, 87U, 40U, 177U, 130U, 267U, 220U, 357U, 310U, 79U, 32U, 169U, 122U, - 259U, 212U, 349U, 302U, 71U, 24U, 161U, 114U, 251U, 204U, 341U, 294U, 63U, 16U, 153U, 106U, 243U, 196U, 333U, 286U, 55U, - 8U, 145U, 98U, 235U, 188U, 325U, 278U, 47U}; - -const unsigned char SCRAMBLER[] = { - 0x00U, 0x00U, 0xD6U, 0xB5U, 0xE2U, 0x30U, 0x82U, 0xFFU, 0x84U, 0x62U, 0xBAU, 0x4EU, 0x96U, 0x90U, 0xD8U, 0x98U, 0xDDU, - 0x5DU, 0x0CU, 0xC8U, 0x52U, 0x43U, 0x91U, 0x1DU, 0xF8U, 0x6EU, 0x68U, 0x2FU, 0x35U, 0xDAU, 0x14U, 0xEAU, 0xCDU, 0x76U, - 0x19U, 0x8DU, 0xD5U, 0x80U, 0xD1U, 0x33U, 0x87U, 0x13U, 0x57U, 0x18U, 0x2DU, 0x29U, 0x78U, 0xC3U}; - -// #define DUMP_M17 - -const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; - -#define WRITE_BIT(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_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) - -CM17Control::CM17Control(const std::string& callsign, unsigned int colorCode, bool selfOnly, bool allowEncryption, CM17Network* network, CDisplay* display, unsigned int timeout, bool duplex, CRSSIInterpolator* rssiMapper) : -m_callsign(callsign), -m_colorCode(colorCode), -m_selfOnly(selfOnly), -m_allowEncryption(allowEncryption), -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_rfFN(0U), -m_rfErrs(0U), -m_rfBits(1U), -m_rfLICH(), -m_rfLICHn(0U), -m_netLICH(), -m_netLICHn(0U), -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) { - std::string source = m_rfLICH.getSource(); - std::string dest = m_rfLICH.getDest(); - - if (m_rssi != 0U) - LogMessage("M17, transmission lost from %s to %s, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), dest.c_str(), float(m_rfFrames) / 25.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); - else - LogMessage("M17, transmission lost from %s to %s, %.1f seconds, BER: %.1f%%", source.c_str(), dest.c_str(), float(m_rfFrames) / 25.0F, 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; - 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++; - } - - unsigned char temp[M17_FRAME_LENGTH_BYTES]; - decorrelator(data + 2U, temp); - interleaver(temp, data + 2U); - - if (m_rfState == RS_RF_LISTENING && data[0U] == TAG_HEADER) { - m_rfLICH.reset(); - - CM17Convolution conv; - unsigned char frame[M17_LICH_LENGTH_BYTES]; - conv.decodeLinkSetup(data + 2U + M17_SYNC_LENGTH_BYTES, frame); - - bool valid = CM17CRC::checkCRC(frame, M17_LICH_LENGTH_BYTES); - if (valid) { - m_rfFrames = 0U; - m_rfErrs = 0U; - m_rfBits = 1U; - m_rfTimeoutTimer.start(); - m_minRSSI = m_rssi; - m_maxRSSI = m_rssi; - m_aveRSSI = m_rssi; - m_rssiCount = 1U; - m_rfLICHn = 0U; - m_rfFN = 0U; - -#if defined(DUMP_M17) - openFile(); -#endif - m_rfLICH.setLinkSetup(frame); - - m_rfState = RS_RF_LATE_ENTRY; - - return true; - } else { - m_rfState = RS_RF_LATE_ENTRY; - } - } - - if (m_rfState == RS_RF_LATE_ENTRY && data[0U] == TAG_DATA) { - unsigned int frag1, frag2, frag3, frag4; - CM17Utils::splitFragmentLICHFEC(data + 2U + M17_SYNC_LENGTH_BYTES, frag1, frag2, frag3, frag4); - - unsigned int lich1 = CGolay24128::decode24128(frag1); - unsigned int lich2 = CGolay24128::decode24128(frag2); - unsigned int lich3 = CGolay24128::decode24128(frag3); - unsigned int lich4 = CGolay24128::decode24128(frag4); - - unsigned int colorCode = (lich4 >> 7) & 0x1FU; - if (colorCode != m_colorCode) - return false; - - bool lateEntry = false; - if (!m_rfLICH.isValid()) { - unsigned char lich[M17_LICH_FRAGMENT_LENGTH_BYTES]; - CM17Utils::combineFragmentLICH(lich1, lich2, lich3, lich4, lich); - - unsigned int n = (lich4 >> 4) & 0x07U; - m_rfLICH.setFragment(lich, n); - - lateEntry = true; - } - - bool valid = m_rfLICH.isValid(); - if (valid) { - m_rfFrames = 0U; - m_rfErrs = 0U; - m_rfBits = 1U; - m_rfTimeoutTimer.start(); - m_minRSSI = m_rssi; - m_maxRSSI = m_rssi; - m_aveRSSI = m_rssi; - m_rssiCount = 1U; - m_rfLICHn = 0U; - -#if defined(DUMP_M17) - openFile(); -#endif - std::string source = m_rfLICH.getSource(); - std::string dest = m_rfLICH.getDest(); - - if (m_selfOnly) { - bool ret = checkCallsign(source); - if (!ret) { - LogMessage("M17, invalid access attempt from %s to %s", source.c_str(), dest.c_str()); - m_rfState = RS_RF_REJECTED; - return false; - } - } - - unsigned char dataType = m_rfLICH.getDataType(); - switch (dataType) { - case 1U: - LogMessage("M17, received RF %s data transmission from %s to %s", lateEntry ? "late entry" : "", source.c_str(), dest.c_str()); - m_rfState = RS_RF_DATA; - break; - case 2U: - LogMessage("M17, received RF %s voice transmission from %s to %s", lateEntry ? "late entry" : "", source.c_str(), dest.c_str()); - m_rfState = RS_RF_AUDIO; - break; - case 3U: - LogMessage("M17, received RF %s voice + data transmission from %s to %s", lateEntry ? "late entry" : "", source.c_str(), dest.c_str()); - m_rfState = RS_RF_AUDIO; - break; - default: - LogMessage("M17, received RF %s unknown transmission from %s to %s", lateEntry ? "late entry" : "", source.c_str(), dest.c_str()); - m_rfState = RS_RF_DATA; - break; - } - - m_display->writeM17(source.c_str(), dest.c_str(), "R"); - - if (m_duplex) { - // Create a Link Setup frame - data[0U] = TAG_HEADER; - data[1U] = 0x00U; - - // Generate the sync - CSync::addM17HeaderSync(data + 2U); - - unsigned char setup[M17_LICH_LENGTH_BYTES]; - m_rfLICH.getLinkSetup(setup); - - // Add the convolution FEC - CM17Convolution conv; - conv.encodeLinkSetup(setup, data + 2U + M17_SYNC_LENGTH_BYTES); - - unsigned char temp[M17_FRAME_LENGTH_BYTES]; - interleaver(data + 2U, temp); - decorrelator(temp, data + 2U); - - writeQueueRF(data); - } - - // Fall through to the next section - } - } - - if ((m_rfState == RS_RF_AUDIO || m_rfState == RS_RF_DATA) && data[0U] == TAG_DATA) { -#if defined(DUMP_M17) - writeFile(data + 2U); -#endif - CM17Convolution conv; - unsigned char frame[M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES]; - conv.decodeData(data + 2U + M17_SYNC_LENGTH_BYTES + M17_LICH_FRAGMENT_FEC_LENGTH_BYTES, frame); - - bool valid = CM17CRC::checkCRC(frame, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES); - if (valid) { - m_rfFN = (frame[0U] << 8) + (frame[1U] << 0); - } else { - // Create a silence frame - m_rfFN++; - - // The new FN - frame[0U] = m_rfFN >> 8; - frame[1U] = m_rfFN >> 0; - - // Add silent audio - unsigned char dataType = m_rfLICH.getDataType(); - switch (dataType) { - case 2U: - ::memcpy(frame + M17_FN_LENGTH_BYTES + 0U, M17_3200_SILENCE, 8U); - ::memcpy(frame + M17_FN_LENGTH_BYTES + 8U, M17_3200_SILENCE, 8U); - break; - case 3U: - ::memcpy(frame + M17_FN_LENGTH_BYTES + 0U, M17_1600_SILENCE, 8U); - break; - default: - break; - } - - // Add the CRC - CM17CRC::encodeCRC(frame, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES); - } - - unsigned char rfData[2U + M17_FRAME_LENGTH_BYTES]; - - rfData[0U] = TAG_DATA; - rfData[1U] = 0x00U; - - // Generate the sync - CSync::addM17DataSync(rfData + 2U); - - unsigned char lich[M17_LICH_FRAGMENT_LENGTH_BYTES]; - m_netLICH.getFragment(lich, m_rfLICHn); - - unsigned int frag1, frag2, frag3, frag4; - CM17Utils::splitFragmentLICH(lich, frag1, frag2, frag3, frag4); - - // Add the Color Code and fragment number - frag4 |= (m_rfLICHn & 0x07U) << 4; - frag4 |= (m_colorCode & 0x1FU) << 7; - - // Add Golay to the LICH fragment here - unsigned int lich1 = CGolay24128::encode24128(frag1); - unsigned int lich2 = CGolay24128::encode24128(frag2); - unsigned int lich3 = CGolay24128::encode24128(frag3); - unsigned int lich4 = CGolay24128::encode24128(frag4); - - CM17Utils::combineFragmentLICHFEC(lich1, lich2, lich3, lich4, rfData + 2U + M17_SYNC_LENGTH_BYTES); - - // Add the Convolution FEC - conv.encodeData(frame, rfData + 2U + M17_SYNC_LENGTH_BYTES + M17_LICH_FRAGMENT_FEC_LENGTH_BYTES); - - // Calculate the BER - if (valid) { - unsigned int errors = 0U; - for (unsigned int i = 2U; i < (M17_FRAME_LENGTH_BYTES + 2U); i++) - errors += countBits(rfData[i] ^ data[i]); - - LogDebug("M17, FN: %u, errs: %u/384 (%.1f%%)", m_rfFN, errors, float(errors) / 3.84F); - - m_rfBits += M17_FRAME_LENGTH_BITS; - m_rfErrs += errors; - - float ber = float(m_rfErrs) / float(m_rfBits); - m_display->writeM17BER(ber); - } - - unsigned char temp[M17_FRAME_LENGTH_BYTES]; - interleaver(rfData + 2U, temp); - decorrelator(rfData, data + 2U); - - if (m_duplex) - writeQueueRF(rfData); - - unsigned char netData[M17_LICH_LENGTH_BYTES + M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES]; - - m_rfLICH.getNetwork(netData + 0U); - - // Copy the FN and payload from the frame - ::memcpy(netData + M17_LICH_LENGTH_BYTES - M17_CRC_LENGTH_BYTES, frame, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES); - - // The CRC is added in the networking code - - writeNetwork(netData); - - m_rfFrames++; - - m_rfLICHn++; - if (m_rfLICHn >= 6U) - m_rfLICHn = 0U; - - // EOT? - if ((m_rfFN & 0x8000U) == 0x8000U) { - std::string source = m_rfLICH.getSource(); - std::string dest = m_rfLICH.getDest(); - - if (m_rssi != 0U) - LogMessage("M17, received RF end of transmission from %s to %s, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", source.c_str(), dest.c_str(), float(m_rfFrames) / 25.0F, 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, %.1f seconds, BER: %.1f%%", source.c_str(), dest.c_str(), float(m_rfFrames) / 25.0F, float(m_rfErrs * 100U) / float(m_rfBits)); - writeEndRF(); - } - - return true; - } - - if (m_rfState == RS_RF_REJECTED && data[0U] == TAG_DATA) { - CM17Convolution conv; - unsigned char frame[M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES]; - conv.decodeData(data + 2U + M17_SYNC_LENGTH_BYTES + M17_LICH_FRAGMENT_FEC_LENGTH_BYTES, frame); - - bool valid = CM17CRC::checkCRC(frame, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES); - if (valid) { - // Handle the EOT for rejected frames - unsigned int fn = (frame[0U] << 8) + (frame[1U] << 0); - if ((fn & 0x8000U) == 0x8000U) - writeEndRF(); - } - - return false; - } - - return false; -} - -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_rfTimeoutTimer.stop(); - - m_rfLICH.reset(); - - 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_netTimeoutTimer.stop(); - m_networkWatchdog.stop(); - m_packetTimer.stop(); - - m_netLICH.reset(); - - m_display->clearM17(); - - if (m_network != NULL) - m_network->reset(); -} - -void CM17Control::writeNetwork() -{ - unsigned char netData[100U]; - 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(); - - m_netLICH.setNetwork(netData); - - if (!m_allowEncryption) { - bool ret = m_rfLICH.isNONCENull(); - if (!ret) - return; - } - - if (m_netState == RS_NET_IDLE) { - std::string source = m_netLICH.getSource(); - std::string dest = m_netLICH.getDest(); - - unsigned char dataType = m_netLICH.getDataType(); - switch (dataType) { - case 1U: - LogMessage("M17, received network data transmission from %s to %s", source.c_str(), dest.c_str()); - m_netState = RS_NET_DATA; - break; - case 2U: - LogMessage("M17, received network voice transmission from %s to %s", source.c_str(), dest.c_str()); - m_netState = RS_NET_AUDIO; - break; - case 3U: - LogMessage("M17, received network voice + data transmission from %s to %s", source.c_str(), dest.c_str()); - m_netState = RS_NET_AUDIO; - break; - default: - LogMessage("M17, received network unknown transmission from %s to %s", source.c_str(), dest.c_str()); - m_netState = RS_NET_DATA; - break; - } - - m_display->writeM17(source.c_str(), dest.c_str(), "N"); - - m_netTimeoutTimer.start(); - m_packetTimer.start(); - m_elapsed.start(); - m_netFrames = 0U; - m_netLICHn = 0U; - - // Create a dummy start message - unsigned char start[M17_FRAME_LENGTH_BYTES + 2U]; - - start[0U] = TAG_HEADER; - start[1U] = 0x00U; - - // Generate the sync - CSync::addM17HeaderSync(start + 2U); - - unsigned char setup[M17_LICH_LENGTH_BYTES]; - m_netLICH.getLinkSetup(setup); - - // Add the convolution FEC - CM17Convolution conv; - conv.encodeLinkSetup(setup, start + 2U + M17_SYNC_LENGTH_BYTES); - - unsigned char temp[M17_FRAME_LENGTH_BYTES]; - interleaver(start + 2U, temp); - decorrelator(temp, start + 2U); - - writeQueueNet(start); - } - - unsigned char data[M17_FRAME_LENGTH_BYTES + 2U]; - - data[0U] = TAG_DATA; - data[1U] = 0x00U; - - // Generate the sync - CSync::addM17DataSync(data + 2U); - - m_netFrames++; - - // Add the fragment LICH - unsigned char lich[M17_LICH_FRAGMENT_LENGTH_BYTES]; - m_netLICH.getFragment(lich, m_netLICHn); - - unsigned int frag1, frag2, frag3, frag4; - CM17Utils::splitFragmentLICH(lich, frag1, frag2, frag3, frag4); - - // Add the Color Code and fragment number - frag4 |= (m_netLICHn & 0x07U) << 4; - frag4 |= (m_colorCode & 0x1FU) << 7; - - // Add Golay to the LICH fragment here - unsigned int lich1 = CGolay24128::encode24128(frag1); - unsigned int lich2 = CGolay24128::encode24128(frag2); - unsigned int lich3 = CGolay24128::encode24128(frag3); - unsigned int lich4 = CGolay24128::encode24128(frag4); - - CM17Utils::combineFragmentLICHFEC(lich1, lich2, lich3, lich4, data + 2U + M17_SYNC_LENGTH_BYTES); - - // Add the FN and the data/audio - unsigned char payload[M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES]; - ::memcpy(payload, netData + 28U, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES); - - // Add the CRC - CM17CRC::encodeCRC(payload, M17_FN_LENGTH_BYTES + M17_PAYLOAD_LENGTH_BYTES + M17_CRC_LENGTH_BYTES); - - // Add the Convolution FEC - CM17Convolution conv; - conv.encodeData(payload, data + 2U + M17_SYNC_LENGTH_BYTES + M17_LICH_FRAGMENT_FEC_LENGTH_BYTES); - - unsigned char temp[M17_FRAME_LENGTH_BYTES]; - interleaver(data + 2U, temp); - decorrelator(temp, data + 2U); - - writeQueueNet(data); - - m_netLICHn++; - if (m_netLICHn >= 6U) - m_netLICHn = 0U; - - // EOT handling - uint16_t fn = (netData[28U] << 8) + (netData[29U] << 0); - if ((fn & 0x8000U) == 0x8000U) { - std::string source = m_netLICH.getSource(); - std::string dest = m_netLICH.getDest(); - LogMessage("M17, received network end of transmission from %s to %s, %.1f seconds", source.c_str(), dest.c_str(), float(m_netFrames) / 25.0F); - writeEndNet(); - } -} - -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) / 25.0F); - 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) -{ - assert(data != NULL); - - if (m_network == NULL) - return; - - if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) - return; - - m_network->write(data); -} - -void CM17Control::interleaver(const unsigned char* in, unsigned char* out) const -{ - assert(in != NULL); - assert(out != NULL); - - for (unsigned int i = 0U; i < (M17_FRAME_LENGTH_BITS - M17_SYNC_LENGTH_BITS); i++) { - unsigned int n1 = i + M17_SYNC_LENGTH_BITS; - bool b = READ_BIT(in, n1) != 0U; - unsigned int n2 = INTERLEAVER[i] + M17_SYNC_LENGTH_BITS; - WRITE_BIT(out, n2, b); - } -} - -void CM17Control::decorrelator(const unsigned char* in, unsigned char* out) const -{ - assert(in != NULL); - assert(out != NULL); - - for (unsigned int i = M17_SYNC_LENGTH_BYTES; i < M17_FRAME_LENGTH_BYTES; i++) { - out[i] = in[i] ^ SCRAMBLER[i]; - } -} - -bool CM17Control::checkCallsign(const std::string& callsign) const -{ - size_t len = m_callsign.size(); - - return m_callsign.compare(0U, len, callsign, 0U, len) == 0; -} - -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_rfTimeoutTimer.stop(); - - // Reset the networking section - m_netState = RS_NET_IDLE; - - m_netTimeoutTimer.stop(); - m_networkWatchdog.stop(); - m_packetTimer.stop(); - } - - m_enabled = enabled; -} - -unsigned int CM17Control::countBits(unsigned char byte) -{ - unsigned int count = 0U; - - const unsigned char* p = &byte; - - for (unsigned int i = 0U; i < 8U; i++) { - if (READ_BIT(p, i) != 0U) - count++; - } - - return count; -} diff --git a/M17Control.h b/M17Control.h deleted file mode 100644 index 276478c..0000000 --- a/M17Control.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 "M17LICH.h" -#include "Display.h" -#include "Defines.h" -#include "Timer.h" -#include "Modem.h" - -#include - -class CM17Control { -public: - CM17Control(const std::string& callsign, unsigned int colorCode, bool selfOnly, bool allowEncryption, 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; - unsigned int m_colorCode; - bool m_selfOnly; - bool m_allowEncryption; - 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_rfFN; - unsigned int m_rfErrs; - unsigned int m_rfBits; - CM17LICH m_rfLICH; - unsigned int m_rfLICHn; - CM17LICH m_netLICH; - unsigned int m_netLICHn; - 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; - - void writeQueueRF(const unsigned char* data); - void writeQueueNet(const unsigned char* data); - void writeNetwork(const unsigned char* data); - void writeNetwork(); - - void interleaver(const unsigned char* in, unsigned char* out) const; - void decorrelator(const unsigned char* in, unsigned char* out) const; - - bool checkCallsign(const std::string& source) const; - - unsigned int countBits(unsigned char byte); - - void writeEndRF(); - void writeEndNet(); - - bool openFile(); - bool writeFile(const unsigned char* data); - void closeFile(); -}; - -#endif diff --git a/M17Convolution.cpp b/M17Convolution.cpp deleted file mode 100644 index 1f64a76..0000000 --- a/M17Convolution.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2009-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. - */ - -#include "M17Convolution.h" - -#include -#include -#include -#include - -const unsigned int PUNCTURE_LIST_LINK_SETUP[] = { - 3U, 6U, 9U, 12U, 19U, 22U, 25U, 28U, 35U, 38U, 41U, 44U, 51U, 54U, 57U, 64U, 67U, 70U, 73U, 80U, 83U, 86U, 89U, 96U, 99U, 102U, - 105U, 112U, 115U, 118U, 125U, 128U, 131U, 134U, 141U, 144U, 147U, 150U, 157U, 160U, 163U, 166U, 173U, 176U, 179U, 186U, 189U, - 192U, 195U, 202U, 205U, 208U, 211U, 218U, 221U, 224U, 227U, 234U, 237U, 240U, 247U, 250U, 253U, 256U, 263U, 266U, 269U, 272U, - 279U, 282U, 285U, 288U, 295U, 298U, 301U, 308U, 311U, 314U, 317U, 324U, 327U, 330U, 333U, 340U, 343U, 346U, 349U, 356U, 359U, - 362U, 369U, 372U, 375U, 378U, 385U, 388U, 391U, 394U, 401U, 404U, 407U, 410U, 417U, 420U, 423U, 430U, 433U, 436U, 439U, 446U, - 449U, 452U, 455U, 462U, 465U, 468U, 471U, 478U, 481U, 484U}; - -const unsigned int PUNCTURE_LIST_DATA[] = { - 5U, 11U, 17U, 20U, 23U, 29U, 35U, 46U, 52U, 58U, 61U, 64U, 70U, 76U, 87U, 93U, 99U, 102U, 105U, 111U, 117U, 128U, 134U, 140U, - 143U, 146U, 152U, 158U, 169U, 175U, 181U, 184U, 187U, 193U, 199U, 210U, 216U, 222U, 225U, 228U, 234U, 240U, 251U, 257U, 263U, - 266U, 269U, 275U, 281U, 292U, 298U, 304U, 307U, 310U, 316U, 322U}; - -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]) - -const uint8_t BRANCH_TABLE1[] = {0U, 0U, 0U, 0U, 2U, 2U, 2U, 2U}; -const uint8_t BRANCH_TABLE2[] = {0U, 2U, 2U, 0U, 0U, 2U, 2U, 0U}; - -const unsigned int NUM_OF_STATES_D2 = 8U; -const unsigned int NUM_OF_STATES = 16U; -const uint32_t M = 4U; -const unsigned int K = 5U; - -CM17Convolution::CM17Convolution() : -m_metrics1(NULL), -m_metrics2(NULL), -m_oldMetrics(NULL), -m_newMetrics(NULL), -m_decisions(NULL), -m_dp(NULL) -{ - m_metrics1 = new uint16_t[16U]; - m_metrics2 = new uint16_t[16U]; - m_decisions = new uint64_t[300U]; -} - -CM17Convolution::~CM17Convolution() -{ - delete[] m_metrics1; - delete[] m_metrics2; - delete[] m_decisions; -} - -void CM17Convolution::encodeLinkSetup(const unsigned char* in, unsigned char* out) const -{ - assert(in != NULL); - assert(out != NULL); - - unsigned char temp1[31U]; - ::memset(temp1, 0x00U, 31U); - ::memcpy(temp1, in, 30U); - - unsigned char temp2[61U]; - encode(temp1, temp2, 244U); - - unsigned int n = 0U; - unsigned int index = 0U; - for (unsigned int i = 0U; i < 488U; i++) { - if (i != PUNCTURE_LIST_LINK_SETUP[index]) { - bool b = READ_BIT1(temp2, i); - WRITE_BIT1(out, n, b); - n++; - } else { - index++; - } - } -} - -void CM17Convolution::encodeData(const unsigned char* in, unsigned char* out) const -{ - assert(in != NULL); - assert(out != NULL); - - unsigned char temp1[21U]; - ::memset(temp1, 0x00U, 21U); - ::memcpy(temp1, in, 20U); - - unsigned char temp2[41U]; - encode(temp1, temp2, 164U); - - unsigned int n = 0U; - unsigned int index = 0U; - for (unsigned int i = 0U; i < 328U; i++) { - if (i != PUNCTURE_LIST_DATA[index]) { - bool b = READ_BIT1(temp2, i); - WRITE_BIT1(out, n, b); - n++; - } else { - index++; - } - } -} - -void CM17Convolution::decodeLinkSetup(const unsigned char* in, unsigned char* out) -{ - assert(in != NULL); - assert(out != NULL); - - uint8_t temp[500U]; - - unsigned int n = 0U; - unsigned int index = 0U; - for (unsigned int i = 0U; i < 368U; i++) { - if (n == PUNCTURE_LIST_LINK_SETUP[index]) { - temp[n++] = 1U; - index++; - } - - bool b = READ_BIT1(in, i); - temp[n++] = b ? 2U : 0U; - } - - for (unsigned int i = 0U; i < 8U; i++) - temp[n++] = 0U; - - start(); - - n = 0U; - for (unsigned int i = 0U; i < 244U; i++) { - uint8_t s0 = temp[n++]; - uint8_t s1 = temp[n++]; - - decode(s0, s1); - } - - chainback(out, 240U); -} - -void CM17Convolution::decodeData(const unsigned char* in, unsigned char* out) -{ - assert(in != NULL); - assert(out != NULL); - - uint8_t temp[350U]; - - unsigned int n = 0U; - unsigned int index = 0U; - for (unsigned int i = 0U; i < 272U; i++) { - if (n == PUNCTURE_LIST_DATA[index]) { - temp[n++] = 1U; - index++; - } - - bool b = READ_BIT1(in, i); - temp[n++] = b ? 2U : 0U; - } - - for (unsigned int i = 0U; i < 8U; i++) - temp[n++] = 0U; - - start(); - - n = 0U; - for (unsigned int i = 0U; i < 164U; i++) { - uint8_t s0 = temp[n++]; - uint8_t s1 = temp[n++]; - - decode(s0, s1); - } - - chainback(out, 160U); -} - -void CM17Convolution::start() -{ - ::memset(m_metrics1, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); - ::memset(m_metrics2, 0x00U, NUM_OF_STATES * sizeof(uint16_t)); - - m_oldMetrics = m_metrics1; - m_newMetrics = m_metrics2; - m_dp = m_decisions; -} - -void CM17Convolution::decode(uint8_t s0, uint8_t s1) -{ - *m_dp = 0U; - - for (uint8_t i = 0U; i < NUM_OF_STATES_D2; i++) { - uint8_t j = i * 2U; - - uint16_t metric = std::abs(BRANCH_TABLE1[i] - s0) + std::abs(BRANCH_TABLE2[i] - s1); - - uint16_t m0 = m_oldMetrics[i] + metric; - uint16_t m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + (M - metric); - uint8_t decision0 = (m0 >= m1) ? 1U : 0U; - m_newMetrics[j + 0U] = decision0 != 0U ? m1 : m0; - - m0 = m_oldMetrics[i] + (M - metric); - m1 = m_oldMetrics[i + NUM_OF_STATES_D2] + metric; - uint8_t decision1 = (m0 >= m1) ? 1U : 0U; - m_newMetrics[j + 1U] = decision1 != 0U ? m1 : m0; - - *m_dp |= (uint64_t(decision1) << (j + 1U)) | (uint64_t(decision0) << (j + 0U)); - } - - ++m_dp; - - assert((m_dp - m_decisions) <= 300); - - uint16_t* tmp = m_oldMetrics; - m_oldMetrics = m_newMetrics; - m_newMetrics = tmp; -} - -void CM17Convolution::chainback(unsigned char* out, unsigned int nBits) -{ - assert(out != NULL); - - uint32_t state = 0U; - - while (nBits-- > 0) { - --m_dp; - - uint32_t i = state >> (9 - K); - uint8_t bit = uint8_t(*m_dp >> i) & 1; - state = (bit << 7) | (state >> 1); - - WRITE_BIT1(out, nBits, bit != 0U); - } -} - -void CM17Convolution::encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const -{ - assert(in != NULL); - assert(out != NULL); - assert(nBits > 0U); - - uint8_t d1 = 0U, d2 = 0U, d3 = 0U, d4 = 0U; - uint32_t k = 0U; - for (unsigned int i = 0U; i < nBits; i++) { - uint8_t d = READ_BIT1(in, i) ? 1U : 0U; - - uint8_t g1 = (d + d3 + d4) & 1; - uint8_t g2 = (d + d1 + d2 + d4) & 1; - - d4 = d3; - d3 = d2; - d2 = d1; - d1 = d; - - WRITE_BIT1(out, k, g1 != 0U); - k++; - - WRITE_BIT1(out, k, g2 != 0U); - k++; - } -} diff --git a/M17Convolution.h b/M17Convolution.h deleted file mode 100644 index e3f5d82..0000000 --- a/M17Convolution.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 - * 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(M17Convolution_H) -#define M17Convolution_H - -#include - -class CM17Convolution { -public: - CM17Convolution(); - ~CM17Convolution(); - - void decodeLinkSetup(const unsigned char* in, unsigned char* out); - void decodeData(const unsigned char* in, unsigned char* out); - - void encodeLinkSetup(const unsigned char* in, unsigned char* out) const; - void encodeData(const unsigned char* in, unsigned char* out) const; - -private: - uint16_t* m_metrics1; - uint16_t* m_metrics2; - uint16_t* m_oldMetrics; - uint16_t* m_newMetrics; - uint64_t* m_decisions; - uint64_t* m_dp; - - void start(); - void decode(uint8_t s0, uint8_t s1); - void chainback(unsigned char* out, unsigned int nBits); - - void encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const; -}; - -#endif diff --git a/M17Defines.h b/M17Defines.h deleted file mode 100644 index 5129d9a..0000000 --- a/M17Defines.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 char M17_HEADER_SYNC_BYTES[] = {0x5DU, 0xDDU}; -const unsigned char M17_DATA_SYNC_BYTES[] = {0xDDU, 0xDDU}; - -const unsigned int M17_SYNC_LENGTH_BITS = 16U; -const unsigned int M17_SYNC_LENGTH_BYTES = M17_SYNC_LENGTH_BITS / 8U; - -const unsigned int M17_LICH_LENGTH_BITS = 240U; -const unsigned int M17_LICH_LENGTH_BYTES = M17_LICH_LENGTH_BITS / 8U; - -const unsigned int M17_LICH_FRAGMENT_LENGTH_BITS = M17_LICH_LENGTH_BITS / 6U; -const unsigned int M17_LICH_FRAGMENT_LENGTH_BYTES = M17_LICH_FRAGMENT_LENGTH_BITS / 8U; - -const unsigned int M17_LICH_FRAGMENT_FEC_LENGTH_BITS = M17_LICH_FRAGMENT_LENGTH_BITS * 2U; -const unsigned int M17_LICH_FRAGMENT_FEC_LENGTH_BYTES = M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 8U; - -const unsigned int M17_PAYLOAD_LENGTH_BITS = 128U; -const unsigned int M17_PAYLOAD_LENGTH_BYTES = M17_PAYLOAD_LENGTH_BITS / 8U; - -const unsigned char M17_NULL_NONCE[] = {0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; -const unsigned int M17_NONCE_LENGTH_BITS = 112U; -const unsigned int M17_NONCE_LENGTH_BYTES = M17_NONCE_LENGTH_BITS / 8U; - -const unsigned int M17_FN_LENGTH_BITS = 16U; -const unsigned int M17_FN_LENGTH_BYTES = M17_FN_LENGTH_BITS / 8U; - -const unsigned int M17_CRC_LENGTH_BITS = 16U; -const unsigned int M17_CRC_LENGTH_BYTES = M17_CRC_LENGTH_BITS / 8U; - -const unsigned char M17_3200_SILENCE[] = {0x01U, 0x00U, 0x09U, 0x43U, 0x9CU, 0xE4U, 0x21U, 0x08U}; -const unsigned char M17_1600_SILENCE[] = {0x01U, 0x00U, 0x04U, 0x00U, 0x25U, 0x75U, 0xDDU, 0xF2U}; - -#endif diff --git a/M17LICH.cpp b/M17LICH.cpp deleted file mode 100644 index e99fd8d..0000000 --- a/M17LICH.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 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 "M17LICH.h" -#include "M17Utils.h" -#include "M17Defines.h" -#include "M17CRC.h" - -#include -#include - -CM17LICH::CM17LICH() : -m_lich(NULL), -m_valid(false) -{ - m_lich = new unsigned char[M17_LICH_LENGTH_BYTES]; -} - -CM17LICH::~CM17LICH() -{ - delete[] m_lich; -} - -void CM17LICH::getNetwork(unsigned char* data) const -{ - assert(data != NULL); - - ::memcpy(data, m_lich, M17_LICH_LENGTH_BYTES); -} - -void CM17LICH::setNetwork(const unsigned char* data) -{ - assert(data != NULL); - - ::memcpy(m_lich, data, M17_LICH_LENGTH_BYTES); - - m_valid = true; -} - -std::string CM17LICH::getSource() const -{ - std::string callsign; - CM17Utils::decodeCallsign(m_lich + 6U, callsign); - - return callsign; -} - -void CM17LICH::setSource(const std::string& callsign) -{ - CM17Utils::encodeCallsign(callsign, m_lich + 6U); -} - -std::string CM17LICH::getDest() const -{ - std::string callsign; - CM17Utils::decodeCallsign(m_lich + 0U, callsign); - - return callsign; -} - -void CM17LICH::setDest(const std::string& callsign) -{ - CM17Utils::encodeCallsign(callsign, m_lich + 0U); -} - -unsigned char CM17LICH::getDataType() const -{ - return (m_lich[13U] >> 1) & 0x03U; -} - -void CM17LICH::setDataType(unsigned char type) -{ - m_lich[13U] &= 0xF9U; - m_lich[13U] |= (type << 1) & 0x06U; -} - -bool CM17LICH::isNONCENull() const -{ - return ::memcmp(m_lich + 14U, M17_NULL_NONCE, M17_NONCE_LENGTH_BYTES) == 0; -} - -void CM17LICH::reset() -{ - ::memset(m_lich, 0x00U, 30U); - - m_valid = false; -} - -bool CM17LICH::isValid() const -{ - return m_valid; -} - -void CM17LICH::getLinkSetup(unsigned char* data) const -{ - assert(data != NULL); - - ::memcpy(data, m_lich, M17_LICH_LENGTH_BYTES); - - CM17CRC::encodeCRC(data, M17_LICH_LENGTH_BYTES); -} - -void CM17LICH::setLinkSetup(const unsigned char* data) -{ - assert(data != NULL); - - ::memcpy(m_lich, data, M17_LICH_LENGTH_BYTES); - - m_valid = CM17CRC::checkCRC(m_lich, M17_LICH_LENGTH_BYTES); -} - -void CM17LICH::getFragment(unsigned char* data, unsigned int n) const -{ - assert(data != NULL); - - CM17CRC::encodeCRC(m_lich, M17_LICH_LENGTH_BYTES); - - ::memcpy(data, m_lich + (n * M17_LICH_FRAGMENT_LENGTH_BYTES), M17_LICH_FRAGMENT_LENGTH_BYTES); -} - -void CM17LICH::setFragment(const unsigned char* data, unsigned int n) -{ - assert(data != NULL); - - ::memcpy(m_lich + (n * M17_LICH_FRAGMENT_LENGTH_BYTES), data, M17_LICH_FRAGMENT_LENGTH_BYTES); - - m_valid = CM17CRC::checkCRC(m_lich, M17_LICH_LENGTH_BYTES); -} diff --git a/M17LICH.h b/M17LICH.h deleted file mode 100644 index 804de3e..0000000 --- a/M17LICH.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 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(M17LICH_H) -#define M17LICH_H - -#include - -class CM17LICH { -public: - CM17LICH(); - ~CM17LICH(); - - void getNetwork(unsigned char* data) const; - void setNetwork(const unsigned char* data); - - std::string getSource() const; - void setSource(const std::string& callsign); - - std::string getDest() const; - void setDest(const std::string& callsign); - - unsigned char getDataType() const; - void setDataType(unsigned char type); - - bool isNONCENull() const; - - void reset(); - bool isValid() const; - - void getLinkSetup(unsigned char* data) const; - void setLinkSetup(const unsigned char* data); - - void getFragment(unsigned char* data, unsigned int n) const; - void setFragment(const unsigned char* data, unsigned int n); - -private: - unsigned char* m_lich; - bool m_valid; -}; - -#endif diff --git a/M17Network.cpp b/M17Network.cpp deleted file mode 100644 index 56a2135..0000000 --- a/M17Network.cpp +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 "M17Utils.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& gwyAddress, unsigned int gwyPort, bool debug) : -m_socket(localPort), -m_addr(), -m_addrLen(0U), -m_debug(debug), -m_enabled(false), -m_outId(0U), -m_inId(0U), -m_buffer(1000U, "M17 Network"), -m_random(), -m_timer(1000U, 5U) -{ - if (CUDPSocket::lookup(gwyAddress, gwyPort, m_addr, m_addrLen) != 0) { - m_addrLen = 0U; - return; - } - - std::random_device rd; - std::mt19937 mt(rd()); - m_random = mt; -} - -CM17Network::~CM17Network() -{ -} - -bool CM17Network::open() -{ - if (m_addrLen == 0U) { - LogError("M17, unable to resolve the gateway address"); - return false; - } - - LogMessage("Opening M17 network connection"); - - bool ret = m_socket.open(m_addr); - - if (ret) { - m_timer.start(); - return true; - } else { - return false; - } -} - -bool CM17Network::write(const unsigned char* data) -{ - if (m_addrLen == 0U) - return false; - - assert(data != NULL); - - unsigned char buffer[100U]; - - buffer[0U] = 'M'; - buffer[1U] = '1'; - buffer[2U] = '7'; - buffer[3U] = ' '; - - // Create a random id for this transmission if needed - if (m_outId == 0U) { - std::uniform_int_distribution dist(0x0001, 0xFFFE); - m_outId = dist(m_random); - } - - buffer[4U] = m_outId / 256U; // Unique session id - buffer[5U] = m_outId % 256U; - - ::memcpy(buffer + 6U, data, 46U); - - // Dummy CRC - buffer[52U] = 0x00U; - buffer[53U] = 0x00U; - - if (m_debug) - CUtils::dump(1U, "M17 data transmitted", buffer, 54U); - - return m_socket.write(buffer, 54U, m_addr, m_addrLen); -} - -void CM17Network::clock(unsigned int ms) -{ - m_timer.clock(ms); - if (m_timer.isRunning() && m_timer.hasExpired()) { - sendPing(); - m_timer.start(); - } - - 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_debug) - CUtils::dump(1U, "M17 Network Data Received", buffer, length); - - if (!m_enabled) - return; - - if (::memcmp(buffer + 0U, "PING", 4U) == 0) - return; - - if (::memcmp(buffer + 0U, "M17 ", 4U) != 0) { - CUtils::dump(2U, "M17, received unknown packet", buffer, length); - return; - } - - uint16_t id = (buffer[4U] << 8) + (buffer[5U] << 0); - if (m_inId == 0U) { - m_inId = id; - } else { - if (id != m_inId) - return; - } - - unsigned char c = length - 6U; - m_buffer.addData(&c, 1U); - - m_buffer.addData(buffer + 6U, length - 6U); -} - -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::reset() -{ - m_outId = 0U; - m_inId = 0U; -} - -void CM17Network::enable(bool enabled) -{ - if (!enabled && m_enabled) - m_buffer.clear(); - - m_enabled = enabled; -} - -void CM17Network::sendPing() -{ - unsigned char buffer[5U]; - - buffer[0U] = 'P'; - buffer[1U] = 'I'; - buffer[2U] = 'N'; - buffer[3U] = 'G'; - - if (m_debug) - CUtils::dump(1U, "M17 data transmitted", buffer, 4U); - - m_socket.write(buffer, 4U, m_addr, m_addrLen); -} diff --git a/M17Network.h b/M17Network.h deleted file mode 100644 index e65e1f3..0000000 --- a/M17Network.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 "Timer.h" - -#include -#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; - uint16_t m_outId; - uint16_t m_inId; - CRingBuffer m_buffer; - std::mt19937 m_random; - CTimer m_timer; - - void sendPing(); -}; - -#endif diff --git a/M17Utils.cpp b/M17Utils.cpp deleted file mode 100644 index 1ba33b1..0000000 --- a/M17Utils.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 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 "M17Utils.h" -#include "M17Defines.h" - -#include - -const std::string M17_CHARS = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."; - -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]) - -void CM17Utils::encodeCallsign(const std::string& callsign, unsigned char* encoded) -{ - assert(encoded != NULL); - - unsigned int len = callsign.size(); - if (len > 9U) - len = 9U; - - uint64_t enc = 0ULL; - for (int i = len - 1; i >= 0; i--) { - size_t pos = M17_CHARS.find(callsign[i]); - if (pos == std::string::npos) - pos = 0ULL; - - enc *= 40ULL; - enc += pos; - } - - encoded[0U] = (enc >> 40) & 0xFFU; - encoded[1U] = (enc >> 32) & 0xFFU; - encoded[2U] = (enc >> 24) & 0xFFU; - encoded[3U] = (enc >> 16) & 0xFFU; - encoded[4U] = (enc >> 8) & 0xFFU; - encoded[5U] = (enc >> 0) & 0xFFU; -} - -void CM17Utils::decodeCallsign(const unsigned char* encoded, std::string& callsign) -{ - assert(encoded != NULL); - - callsign.clear(); - - uint64_t enc = (uint64_t(encoded[0U]) << 40) + - (uint64_t(encoded[1U]) << 32) + - (uint64_t(encoded[2U]) << 24) + - (uint64_t(encoded[3U]) << 16) + - (uint64_t(encoded[4U]) << 8) + - (uint64_t(encoded[5U]) << 0); - - if (enc >= 262144000000000ULL) // 40^9 - return; - - while (enc > 0ULL) { - callsign += " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."[enc % 40ULL]; - enc /= 40ULL; - } -} - -void CM17Utils::splitFragmentLICH(const unsigned char* data, unsigned int& frag1, unsigned int& frag2, unsigned int& frag3, unsigned int& frag4) -{ - assert(data != NULL); - - frag1 = frag2 = frag3 = frag4 = 0x00U; - - unsigned int offset = 0U; - unsigned int MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag1 |= MASK; - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag2 |= MASK; - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag3 |= MASK; - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag4 |= MASK; - } -} - -void CM17Utils::splitFragmentLICHFEC(const unsigned char* data, unsigned int& frag1, unsigned int& frag2, unsigned int& frag3, unsigned int& frag4) -{ - assert(data != NULL); - - frag1 = frag2 = frag3 = frag4 = 0x00U; - - unsigned int offset = 0U; - unsigned int MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag1 |= MASK; - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag2 |= MASK; - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag3 |= MASK; - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = READ_BIT1(data, offset) != 0x00U; - if (b) - frag4 |= MASK; - } -} - -void CM17Utils::combineFragmentLICH(unsigned int frag1, unsigned int frag2, unsigned int frag3, unsigned int frag4, unsigned char* data) -{ - assert(data != NULL); - - unsigned int offset = 0U; - unsigned int MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag1 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag2 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag3 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag4 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } -} - -void CM17Utils::combineFragmentLICHFEC(unsigned int frag1, unsigned int frag2, unsigned int frag3, unsigned int frag4, unsigned char* data) -{ - assert(data != NULL); - - unsigned int offset = 0U; - unsigned int MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag1 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag2 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag3 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } - - MASK = 0x800000U; - for (unsigned int i = 0U; i < (M17_LICH_FRAGMENT_FEC_LENGTH_BITS / 4U); i++, offset++, MASK >>= 1) { - bool b = (frag4 & MASK) == MASK; - WRITE_BIT1(data, offset, b); - } -} diff --git a/M17Utils.h b/M17Utils.h deleted file mode 100644 index c6e27ce..0000000 --- a/M17Utils.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 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(M17Utils_H) -#define M17Utils_H - -#include - -class CM17Utils { -public: - CM17Utils(); - ~CM17Utils(); - - static void encodeCallsign(const std::string& callsign, unsigned char* encoded); - static void decodeCallsign(const unsigned char* encoded, std::string& callsign); - - static void splitFragmentLICH(const unsigned char* data, unsigned int& frag1, unsigned int& frag2, unsigned int& frag3, unsigned int& frag4); - static void splitFragmentLICHFEC(const unsigned char* data, unsigned int& frag1, unsigned int& frag2, unsigned int& frag3, unsigned int& frag4); - - static void combineFragmentLICH(unsigned int frag1, unsigned int frag2, unsigned int frag3, unsigned int frag4, unsigned char* data); - static void combineFragmentLICHFEC(unsigned int frag1, unsigned int frag2, unsigned int frag3, unsigned int frag4, unsigned char* data); - -private: -}; - -#endif diff --git a/MMDVM.ini b/MMDVM.ini index 7810965..2b3dcd1 100644 --- a/MMDVM.ini +++ b/MMDVM.ini @@ -44,10 +44,9 @@ Time=24 [Modem] # Port=/dev/ttyACM0 -Port=/dev/ttyAMA0 -# Port=\\.\COM4 +# Port=/dev/ttyAMA0 +Port=\\.\COM4 Protocol=uart -Speed=460800 # Address=0x22 TXInvert=1 RXInvert=0 @@ -67,10 +66,8 @@ RFLevel=100 # YSFTXLevel=50 # P25TXLevel=50 # NXDNTXLevel=50 -# M17TXLevel-50 # POCSAGTXLevel=50 # FMTXLevel=50 -# AX25TXLevel=50 RSSIMappingFile=RSSI.dat UseCOSAsLockout=0 Trace=0 @@ -83,6 +80,11 @@ RemotePort=40094 LocalPort=40095 # SendFrameType=0 +[UMP] +Enable=0 +# Port=\\.\COM4 +Port=/dev/ttyACM1 + [D-Star] Enable=1 Module=C @@ -138,13 +140,6 @@ RemoteGateway=0 TXHang=5 # ModeHang=10 -[M17] -Enable=1 -ColorCode=3 -SelfOnly=0 -TXHang=5 -# ModeHang=10 - [POCSAG] Enable=1 Frequency=439987500 @@ -179,22 +174,9 @@ KerchunkTime=0 HangTime=7 AccessMode=1 COSInvert=0 -NoiseSquelch=0 -SquelchThreshold=30 -# SquelchHighThreshold=30 -# SquelchLowThreshold=20 RFAudioBoost=1 MaxDevLevel=90 ExtAudioBoost=1 -# ModeHang=10 - -[AX.25] -Enable=1 -TXDelay=300 -RXTwist=6 -SlotTime=30 -PPersist=128 -Trace=1 [D-Star Network] Enable=1 @@ -247,14 +229,6 @@ GatewayPort=14020 # ModeHang=3 Debug=0 -[M17 Network] -Enable=1 -GatewayAddress=127.0.0.1 -GatewayPort=17010 -LocalPort=17011 -# ModeHang=3 -Debug=0 - [POCSAG Network] Enable=1 LocalAddress=127.0.0.1 @@ -264,22 +238,6 @@ GatewayPort=4800 # ModeHang=3 Debug=0 -[FM Network] -Enable=1 -LocalAddress=127.0.0.1 -LocalPort=3810 -GatewayAddress=127.0.0.1 -GatewayPort=4810 -SampleRate=8000 -# ModeHang=3 -Debug=0 - -[AX.25 Network] -Enable=1 -Port=/dev/ttyp7 -Speed=9600 -Debug=0 - [TFT Serial] # Port=modem Port=/dev/ttyAMA0 diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index 8c66568..c1dbdba 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -23,8 +23,6 @@ #include "NXDNIcomNetwork.h" #include "RSSIInterpolator.h" #include "SerialController.h" -#include "SerialModem.h" -#include "NullModem.h" #include "Version.h" #include "StopWatch.h" #include "Defines.h" @@ -124,36 +122,27 @@ m_dmr(NULL), m_ysf(NULL), m_p25(NULL), m_nxdn(NULL), -m_m17(NULL), m_pocsag(NULL), -m_fm(NULL), -m_ax25(NULL), m_dstarNetwork(NULL), m_dmrNetwork(NULL), m_ysfNetwork(NULL), m_p25Network(NULL), m_nxdnNetwork(NULL), -m_m17Network(NULL), m_pocsagNetwork(NULL), -m_fmNetwork(NULL), -m_ax25Network(NULL), m_display(NULL), +m_ump(NULL), m_mode(MODE_IDLE), m_dstarRFModeHang(10U), m_dmrRFModeHang(10U), m_ysfRFModeHang(10U), m_p25RFModeHang(10U), m_nxdnRFModeHang(10U), -m_m17RFModeHang(10U), -m_fmRFModeHang(10U), m_dstarNetModeHang(3U), m_dmrNetModeHang(3U), m_ysfNetModeHang(3U), m_p25NetModeHang(3U), m_nxdnNetModeHang(3U), -m_m17NetModeHang(3U), m_pocsagNetModeHang(3U), -m_fmNetModeHang(3U), m_modeTimer(1000U), m_dmrTXTimer(1000U), m_cwIdTimer(1000U), @@ -164,10 +153,8 @@ m_dmrEnabled(false), m_ysfEnabled(false), m_p25Enabled(false), m_nxdnEnabled(false), -m_m17Enabled(false), m_pocsagEnabled(false), m_fmEnabled(false), -m_ax25Enabled(false), m_cwIdTime(0U), m_dmrLookup(NULL), m_nxdnLookup(NULL), @@ -286,7 +273,21 @@ int CMMDVMHost::run() if (!ret) return 1; - m_display = CDisplay::createDisplay(m_conf, m_modem); + if (m_conf.getUMPEnabled()) { + std::string port = m_conf.getUMPPort(); + + LogInfo("Universal MMDVM Peripheral"); + LogInfo(" Port: %s", port.c_str()); + + m_ump = new CUMP(port); + bool ret = m_ump->open(); + if (!ret) { + delete m_ump; + m_ump = NULL; + } + } + + m_display = CDisplay::createDisplay(m_conf,m_ump,m_modem); if (m_dstarEnabled && m_conf.getDStarNetworkEnabled()) { ret = createDStarNetwork(); @@ -318,30 +319,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) return 1; } - if (m_fmEnabled && m_conf.getFMNetworkEnabled()) { - ret = createFMNetwork(); - if (!ret) - return 1; - } - - if (m_ax25Enabled && m_conf.getAX25NetworkEnabled()) { - ret = createAX25Network(); - if (!ret) - return 1; - } - sockaddr_storage transparentAddress; unsigned int transparentAddrLen; CUDPSocket* transparentSocket = NULL; @@ -518,6 +501,7 @@ int CMMDVMHost::run() else if (ovcm == DMR_OVCM_ON) LogInfo(" OVCM: on"); + switch (dmrBeacons) { case DMR_BEACONS_NETWORK: { unsigned int dmrBeaconDuration = m_conf.getDMRBeaconDuration(); @@ -620,23 +604,6 @@ 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 colorCode = m_conf.getM17ColorCode(); - bool allowEncryption = m_conf.getM17AllowEncryption(); - unsigned int txHang = m_conf.getM17TXHang(); - m_m17RFModeHang = m_conf.getM17ModeHang(); - - LogInfo("M17 RF Parameters"); - LogInfo(" Self Only: %s", selfOnly ? "yes" : "no"); - LogInfo(" Color Code: %u", colorCode); - LogInfo(" Allow Encryption: %s", allowEncryption ? "yes" : "no"); - LogInfo(" TX Hang: %us", txHang); - LogInfo(" Mode Hang: %us", m_m17RFModeHang); - - m_m17 = new CM17Control(m_callsign, colorCode, selfOnly, allowEncryption, m_m17Network, m_display, m_timeout, m_duplex, rssi); - } - CTimer pocsagTimer(1000U, 30U); if (m_pocsagEnabled) { @@ -651,29 +618,6 @@ int CMMDVMHost::run() pocsagTimer.start(); } - if (m_ax25Enabled) { - unsigned int txDelay = m_conf.getAX25TXDelay(); - int rxTwist = m_conf.getAX25RXTwist(); - unsigned int slotTime = m_conf.getAX25SlotTime(); - unsigned int pPersist = m_conf.getAX25PPersist(); - bool trace = m_conf.getAX25Trace(); - - LogInfo("AX.25 RF Parameters"); - LogInfo(" TX Delay: %ums", txDelay); - LogInfo(" RX Twist: %d", rxTwist); - LogInfo(" Slot Time: %ums", slotTime); - LogInfo(" P-Persist: %u", pPersist); - LogInfo(" Trace: %s", trace ? "yes" : "no"); - - m_ax25 = new CAX25Control(m_ax25Network, trace); - } - - if (m_fmEnabled) { - m_fmRFModeHang = m_conf.getFMModeHang(); - - m_fm = new CFMControl(m_fmNetwork); - } - bool remoteControlEnabled = m_conf.getRemoteControlEnabled(); if (remoteControlEnabled) { std::string address = m_conf.getRemoteControlAddress(); @@ -697,11 +641,14 @@ int CMMDVMHost::run() LogMessage("MMDVMHost-%s is running", VERSION); while (!m_killed) { - bool lockout = m_modem->hasLockout(); + bool lockout1 = m_modem->hasLockout(); + bool lockout2 = false; - if (lockout && m_mode != MODE_LOCKOUT) + if (m_ump != NULL) + lockout2 = m_ump->getLockout(); + if ((lockout1 || lockout2) && m_mode != MODE_LOCKOUT) setMode(MODE_LOCKOUT); - else if (!lockout && m_mode == MODE_LOCKOUT) + else if ((!lockout1 && !lockout2) && m_mode == MODE_LOCKOUT) setMode(MODE_IDLE); bool error = m_modem->hasError(); @@ -710,7 +657,20 @@ int CMMDVMHost::run() else if (!error && m_mode == MODE_ERROR) setMode(MODE_IDLE); - unsigned char data[500U]; + unsigned char mode = m_modem->getMode(); + if (mode == MODE_FM && m_mode != MODE_FM) + setMode(mode); + else if (mode != MODE_FM && m_mode == MODE_FM) + setMode(mode); + + if (m_ump != NULL) { + bool tx = m_modem->hasTX(); + m_ump->setTX(tx); + bool cd = m_modem->hasCD(); + m_ump->setCD(cd); + } + + unsigned char data[MODEM_DATA_LEN]; unsigned int len; bool ret; @@ -852,47 +812,6 @@ 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->readFMData(data); - if (m_fm != NULL && len > 0U) { - if (m_mode == MODE_IDLE) { - bool ret = m_fm->writeModem(data, len); - if (ret) { - m_modeTimer.setTimeout(m_fmRFModeHang); - setMode(MODE_FM); - } - } else if (m_mode == MODE_FM) { - m_fm->writeModem(data, len); - m_modeTimer.start(); - } else if (m_mode != MODE_LOCKOUT) { - LogWarning("FM modem data received when in mode %u", m_mode); - } - } - - len = m_modem->readAX25Data(data); - if (m_ax25 != NULL && len > 0U) { - if (m_mode == MODE_IDLE || m_mode == MODE_FM) { - m_ax25->writeModem(data, len); - } else if (m_mode != MODE_LOCKOUT) { - LogWarning("NXDN 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); @@ -1024,25 +943,6 @@ 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) { @@ -1062,40 +962,6 @@ int CMMDVMHost::run() } } - if (m_fm != NULL) { - unsigned int space = m_modem->getFMSpace(); - if (space > 0U) { - len = m_fm->readModem(data, space); - if (len > 0U) { - if (m_mode == MODE_IDLE) { - m_modeTimer.setTimeout(m_fmNetModeHang); - setMode(MODE_FM); - } - if (m_mode == MODE_FM) { - m_modem->writeFMData(data, len); - m_modeTimer.start(); - } else if (m_mode != MODE_LOCKOUT) { - LogWarning("FM data received when in mode %u", m_mode); - } - } - } - } - - if (m_ax25 != NULL) { - ret = m_modem->hasAX25Space(); - if (ret) { - len = m_ax25->readModem(data); - if (len > 0U) { - if (m_mode == MODE_IDLE || m_mode == MODE_FM) { - m_modem->writeAX25Data(data, len); - } - else if (m_mode != MODE_LOCKOUT) { - LogWarning("AX.25 data received when in mode %u", m_mode); - } - } - } - } - if (transparentSocket != NULL) { sockaddr_storage address; unsigned int addrlen; @@ -1126,12 +992,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); - if (m_fm != NULL) - m_fm->clock(ms); if (m_dstarNetwork != NULL) m_dstarNetwork->clock(ms); @@ -1143,12 +1005,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); - if (m_fmNetwork != NULL) - m_fmNetwork->clock(ms); m_cwIdTimer.clock(ms); if (m_cwIdTimer.isRunning() && m_cwIdTimer.hasExpired()) { @@ -1210,6 +1068,9 @@ int CMMDVMHost::run() pocsagTimer.start(); } + if (m_ump != NULL) + m_ump->clock(ms); + if (ms < 5U) CThread::sleep(5U); } @@ -1222,6 +1083,11 @@ int CMMDVMHost::run() m_display->close(); delete m_display; + if (m_ump != NULL) { + m_ump->close(); + delete m_ump; + } + if (m_dmrLookup != NULL) m_dmrLookup->stop(); @@ -1253,26 +1119,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; } - if (m_fmNetwork != NULL) { - m_fmNetwork->close(); - delete m_fmNetwork; - } - - if (m_ax25Network != NULL) { - m_ax25Network->close(); - delete m_ax25Network; - } - if (transparentSocket != NULL) { transparentSocket->close(); delete transparentSocket; @@ -1288,10 +1139,7 @@ int CMMDVMHost::run() delete m_ysf; delete m_p25; delete m_nxdn; - delete m_m17; delete m_pocsag; - delete m_fm; - delete m_ax25; return 0; } @@ -1300,7 +1148,6 @@ bool CMMDVMHost::createModem() { std::string port = m_conf.getModemPort(); std::string protocol = m_conf.getModemProtocol(); - unsigned int speed = m_conf.getModemSpeed(); unsigned int address = m_conf.getModemAddress(); bool rxInvert = m_conf.getModemRXInvert(); bool txInvert = m_conf.getModemTXInvert(); @@ -1314,10 +1161,8 @@ 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(); - float ax25TXLevel = m_conf.getModemAX25TXLevel(); bool trace = m_conf.getModemTrace(); bool debug = m_conf.getModemDebug(); unsigned int colorCode = m_conf.getDMRColorCode(); @@ -1325,7 +1170,6 @@ 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(); @@ -1334,21 +1178,13 @@ bool CMMDVMHost::createModem() int rxDCOffset = m_conf.getModemRXDCOffset(); int txDCOffset = m_conf.getModemTXDCOffset(); float rfLevel = m_conf.getModemRFLevel(); - int rxTwist = m_conf.getAX25RXTwist(); - unsigned int ax25TXDelay = m_conf.getAX25TXDelay(); - unsigned int ax25SlotTime = m_conf.getAX25SlotTime(); - unsigned int ax25PPersist = m_conf.getAX25PPersist(); bool useCOSAsLockout = m_conf.getModemUseCOSAsLockout(); LogInfo("Modem Parameters"); LogInfo(" Port: %s", port.c_str()); -#if defined(__linux__) LogInfo(" Protocol: %s", protocol.c_str()); if (protocol == "i2c") - LogInfo(" I2C Address: %02X", address); - else -#endif - LogInfo(" Speed: %u", speed); + LogInfo(" i2c Address: %02X", address); LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no"); LogInfo(" TX Invert: %s", txInvert ? "yes" : "no"); LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no"); @@ -1366,61 +1202,52 @@ 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(" AX.25 TX Level: %.1f%%", ax25TXLevel); LogInfo(" TX Frequency: %uHz (%uHz)", txFrequency, txFrequency + txOffset); LogInfo(" Use COS as Lockout: %s", useCOSAsLockout ? "yes" : "no"); - if (port == "NullModem") - m_modem = new CNullModem; - else - m_modem = new CSerialModem(port, m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); - m_modem->setSerialParams(protocol, address, speed); - m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_m17Enabled, m_pocsagEnabled, m_fmEnabled, m_ax25Enabled); - m_modem->setLevels(rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel); + 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->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); - m_modem->setAX25Params(rxTwist, ax25TXDelay, ax25SlotTime, ax25PPersist); if (m_fmEnabled) { - std::string callsign = m_conf.getFMCallsign(); - unsigned int callsignSpeed = m_conf.getFMCallsignSpeed(); - unsigned int callsignFrequency = m_conf.getFMCallsignFrequency(); - unsigned int callsignTime = m_conf.getFMCallsignTime(); - unsigned int callsignHoldoff = m_conf.getFMCallsignHoldoff(); - float callsignHighLevel = m_conf.getFMCallsignHighLevel(); - float callsignLowLevel = m_conf.getFMCallsignLowLevel(); - bool callsignAtStart = m_conf.getFMCallsignAtStart(); - bool callsignAtEnd = m_conf.getFMCallsignAtEnd(); - bool callsignAtLatch = m_conf.getFMCallsignAtLatch(); - std::string rfAck = m_conf.getFMRFAck(); - unsigned int ackSpeed = m_conf.getFMAckSpeed(); - unsigned int ackFrequency = m_conf.getFMAckFrequency(); - unsigned int ackMinTime = m_conf.getFMAckMinTime(); - unsigned int ackDelay = m_conf.getFMAckDelay(); - float ackLevel = m_conf.getFMAckLevel(); - unsigned int timeout = m_conf.getFMTimeout(); - float timeoutLevel = m_conf.getFMTimeoutLevel(); - float ctcssFrequency = m_conf.getFMCTCSSFrequency(); - unsigned int ctcssHighThreshold = m_conf.getFMCTCSSHighThreshold(); - unsigned int ctcssLowThreshold = m_conf.getFMCTCSSLowThreshold(); - float ctcssLevel = m_conf.getFMCTCSSLevel(); - unsigned int kerchunkTime = m_conf.getFMKerchunkTime(); - unsigned int hangTime = m_conf.getFMHangTime(); - unsigned int accessMode = m_conf.getFMAccessMode(); - bool cosInvert = m_conf.getFMCOSInvert(); - bool noiseSquelch = m_conf.getFMNoiseSquelch(); - unsigned int squelchHighThreshold = m_conf.getFMSquelchHighThreshold(); - unsigned int squelchLowThreshold = m_conf.getFMSquelchLowThreshold(); - unsigned int rfAudioBoost = m_conf.getFMRFAudioBoost(); - float maxDevLevel = m_conf.getFMMaxDevLevel(); - unsigned int modeHangTime = m_conf.getFMModeHang(); + std::string callsign = m_conf.getFMCallsign(); + unsigned int callsignSpeed = m_conf.getFMCallsignSpeed(); + unsigned int callsignFrequency = m_conf.getFMCallsignFrequency(); + unsigned int callsignTime = m_conf.getFMCallsignTime(); + unsigned int callsignHoldoff = m_conf.getFMCallsignHoldoff(); + float callsignHighLevel = m_conf.getFMCallsignHighLevel(); + float callsignLowLevel = m_conf.getFMCallsignLowLevel(); + bool callsignAtStart = m_conf.getFMCallsignAtStart(); + bool callsignAtEnd = m_conf.getFMCallsignAtEnd(); + bool callsignAtLatch = m_conf.getFMCallsignAtLatch(); + std::string rfAck = m_conf.getFMRFAck(); + std::string extAck = m_conf.getFMExtAck(); + unsigned int ackSpeed = m_conf.getFMAckSpeed(); + unsigned int ackFrequency = m_conf.getFMAckFrequency(); + unsigned int ackMinTime = m_conf.getFMAckMinTime(); + unsigned int ackDelay = m_conf.getFMAckDelay(); + float ackLevel = m_conf.getFMAckLevel(); + unsigned int timeout = m_conf.getFMTimeout(); + float timeoutLevel = m_conf.getFMTimeoutLevel(); + float ctcssFrequency = m_conf.getFMCTCSSFrequency(); + unsigned int ctcssHighThreshold = m_conf.getFMCTCSSHighThreshold(); + unsigned int ctcssLowThreshold = m_conf.getFMCTCSSLowThreshold(); + float ctcssLevel = m_conf.getFMCTCSSLevel(); + unsigned int kerchunkTime = m_conf.getFMKerchunkTime(); + unsigned int hangTime = m_conf.getFMHangTime(); + unsigned int accessMode = m_conf.getFMAccessMode(); + bool cosInvert = m_conf.getFMCOSInvert(); + unsigned int rfAudioBoost = m_conf.getFMRFAudioBoost(); + float maxDevLevel = m_conf.getFMMaxDevLevel(); + unsigned int extAudioBoost = m_conf.getFMExtAudioBoost(); LogInfo("FM Parameters"); LogInfo(" Callsign: %s", callsign.c_str()); @@ -1434,6 +1261,7 @@ bool CMMDVMHost::createModem() LogInfo(" Callsign At End: %s", callsignAtEnd ? "yes" : "no"); LogInfo(" Callsign At Latch: %s", callsignAtLatch ? "yes" : "no"); LogInfo(" RF Ack: %s", rfAck.c_str()); + // LogInfo(" Ext. Ack: %s", extAck.c_str()); LogInfo(" Ack Speed: %uWPM", ackSpeed); LogInfo(" Ack Frequency: %uHz", ackFrequency); LogInfo(" Ack Min Time: %us", ackMinTime); @@ -1449,30 +1277,13 @@ bool CMMDVMHost::createModem() LogInfo(" Hang Time: %us", hangTime); LogInfo(" Access Mode: %u", accessMode); LogInfo(" COS Invert: %s", cosInvert ? "yes" : "no"); - - LogInfo(" Noise Squelch: %s", noiseSquelch ? "yes" : "no"); - if (noiseSquelch) { - LogInfo(" Squelch High Threshold: %u", squelchHighThreshold); - LogInfo(" Squelch Low Threshold: %u", squelchLowThreshold); - } - LogInfo(" RF Audio Boost: x%u", rfAudioBoost); LogInfo(" Max. Deviation Level: %.1f%%", maxDevLevel); - LogInfo(" Mode Hang: %us", modeHangTime); + // LogInfo(" Ext. Audio Boost: x%u", extAudioBoost); m_modem->setFMCallsignParams(callsign, callsignSpeed, callsignFrequency, callsignTime, callsignHoldoff, callsignHighLevel, callsignLowLevel, callsignAtStart, callsignAtEnd, callsignAtLatch); m_modem->setFMAckParams(rfAck, ackSpeed, ackFrequency, ackMinTime, ackDelay, ackLevel); - m_modem->setFMMiscParams(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, accessMode, cosInvert, noiseSquelch, squelchHighThreshold, squelchLowThreshold, rfAudioBoost, maxDevLevel); - - if (m_conf.getFMNetworkEnabled()) { - std::string extAck = m_conf.getFMExtAck(); - unsigned int extAudioBoost = m_conf.getFMExtAudioBoost(); - - LogInfo(" Ext. Ack: %s", extAck.c_str()); - LogInfo(" Ext. Audio Boost: x%u", extAudioBoost); - - m_modem->setFMExtParams(extAck, extAudioBoost); - } + m_modem->setFMMiscParams(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, accessMode, cosInvert, rfAudioBoost, maxDevLevel); } bool ret = m_modem->open(); @@ -1690,33 +1501,6 @@ 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(); @@ -1747,62 +1531,6 @@ bool CMMDVMHost::createPOCSAGNetwork() return true; } -bool CMMDVMHost::createFMNetwork() -{ - std::string gatewayAddress = m_conf.getFMGatewayAddress(); - unsigned int gatewayPort = m_conf.getFMGatewayPort(); - std::string localAddress = m_conf.getFMLocalAddress(); - unsigned int localPort = m_conf.getFMLocalPort(); - unsigned int sampleRate = m_conf.getFMSampleRate(); - m_fmNetModeHang = m_conf.getFMNetworkModeHang(); - bool debug = m_conf.getFMNetworkDebug(); - - LogInfo("FM Network Parameters"); - LogInfo(" Gateway Address: %s", gatewayAddress.c_str()); - LogInfo(" Gateway Port: %u", gatewayPort); - LogInfo(" Local Address: %s", localAddress.c_str()); - LogInfo(" Local Port: %u", localPort); - LogInfo(" Sample Rate: %u", sampleRate); - LogInfo(" Mode Hang: %us", m_fmNetModeHang); - - m_fmNetwork = new CFMNetwork(localAddress, localPort, gatewayAddress, gatewayPort, sampleRate, debug); - - bool ret = m_fmNetwork->open(); - if (!ret) { - delete m_fmNetwork; - m_fmNetwork = NULL; - return false; - } - - m_fmNetwork->enable(true); - - return true; -} - -bool CMMDVMHost::createAX25Network() -{ - std::string port = m_conf.getAX25NetworkPort(); - unsigned int speed = m_conf.getAX25NetworkSpeed(); - bool debug = m_conf.getAX25NetworkDebug(); - - LogInfo("AX.25 Network Parameters"); - LogInfo(" Port: %s", port.c_str()); - LogInfo(" Speed: %u", speed); - - m_ax25Network = new CAX25Network(port, speed, debug); - - bool ret = m_ax25Network->open(); - if (!ret) { - delete m_ax25Network; - m_ax25Network = NULL; - return false; - } - - m_ax25Network->enable(true); - - return true; -} - void CMMDVMHost::readParams() { m_dstarEnabled = m_conf.getDStarEnabled(); @@ -1810,10 +1538,8 @@ 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_ax25Enabled = m_conf.getAX25Enabled(); m_duplex = m_conf.getDuplex(); m_callsign = m_conf.getCallsign(); m_id = m_conf.getId(); @@ -1829,10 +1555,8 @@ 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"); - LogInfo(" AX.25: %s", m_ax25Enabled ? "enabled" : "disabled"); } void CMMDVMHost::setMode(unsigned char mode) @@ -1852,14 +1576,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(true); if (m_dmr != NULL) @@ -1870,15 +1588,11 @@ 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_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); m_modem->setMode(MODE_DSTAR); + if (m_ump != NULL) + m_ump->setMode(MODE_DSTAR); m_mode = MODE_DSTAR; m_modeTimer.start(); m_cwIdTimer.stop(); @@ -1896,14 +1610,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -1914,15 +1622,11 @@ 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_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); m_modem->setMode(MODE_DMR); + if (m_ump != NULL) + m_ump->setMode(MODE_DMR); if (m_duplex) { m_modem->writeDMRStart(true); m_dmrTXTimer.start(); @@ -1944,14 +1648,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -1962,15 +1660,11 @@ 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_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); m_modem->setMode(MODE_YSF); + if (m_ump != NULL) + m_ump->setMode(MODE_YSF); m_mode = MODE_YSF; m_modeTimer.start(); m_cwIdTimer.stop(); @@ -1988,14 +1682,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2006,15 +1694,11 @@ 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); - if (m_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); m_modem->setMode(MODE_P25); + if (m_ump != NULL) + m_ump->setMode(MODE_P25); m_mode = MODE_P25; m_modeTimer.start(); m_cwIdTimer.stop(); @@ -2032,14 +1716,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2050,57 +1728,17 @@ 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); - if (m_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->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(); 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); - m_mode = MODE_M17; - m_modeTimer.start(); - m_cwIdTimer.stop(); - createLockFile("M17"); - break; - case MODE_POCSAG: if (m_dstarNetwork != NULL) m_dstarNetwork->enable(false); @@ -2112,14 +1750,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2130,15 +1762,11 @@ 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); - if (m_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); m_modem->setMode(MODE_POCSAG); + if (m_ump != NULL) + m_ump->setMode(MODE_POCSAG); m_mode = MODE_POCSAG; m_modeTimer.start(); m_cwIdTimer.stop(); @@ -2156,14 +1784,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_fmNetwork != NULL) - m_fmNetwork->enable(true); - if (m_ax25Network != NULL) - m_ax25Network->enable(true); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2174,22 +1796,17 @@ 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_fm != NULL) - m_fm->enable(true); - if (m_ax25 != NULL) - m_ax25->enable(true); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } - m_modem->setMode(MODE_FM); + if (m_ump != NULL) + m_ump->setMode(MODE_FM); m_display->setFM(); m_mode = MODE_FM; - m_modeTimer.start(); + m_modeTimer.stop(); m_cwIdTimer.stop(); createLockFile("FM"); break; @@ -2205,14 +1822,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2223,19 +1834,15 @@ 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_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } m_modem->setMode(MODE_IDLE); + if (m_ump != NULL) + m_ump->setMode(MODE_IDLE); m_display->setLockout(); m_mode = MODE_LOCKOUT; m_modeTimer.stop(); @@ -2255,14 +1862,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_fmNetwork != NULL) - m_fmNetwork->enable(false); - if (m_ax25Network != NULL) - m_ax25Network->enable(false); if (m_dstar != NULL) m_dstar->enable(false); if (m_dmr != NULL) @@ -2273,18 +1874,14 @@ 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_fm != NULL) - m_fm->enable(false); - if (m_ax25 != NULL) - m_ax25->enable(false); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } + if (m_ump != NULL) + m_ump->setMode(MODE_IDLE); m_display->setError("MODEM"); m_mode = MODE_ERROR; m_modeTimer.stop(); @@ -2303,14 +1900,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_fmNetwork != NULL) - m_fmNetwork->enable(true); - if (m_ax25Network != NULL) - m_ax25Network->enable(true); if (m_dstar != NULL) m_dstar->enable(true); if (m_dmr != NULL) @@ -2321,19 +1912,15 @@ 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_fm != NULL) - m_fm->enable(true); - if (m_ax25 != NULL) - m_ax25->enable(true); if (m_mode == MODE_DMR && m_duplex && m_modem->hasTX()) { m_modem->writeDMRStart(false); m_dmrTXTimer.stop(); } m_modem->setMode(MODE_IDLE); + if (m_ump != NULL) + m_ump->setMode(MODE_IDLE); if (m_mode == MODE_ERROR) { m_modem->sendCWId(m_callsign); m_cwIdTimer.setTimeout(m_cwIdTime); @@ -2404,102 +1991,78 @@ 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) + if (m_fmEnabled != false) processModeCommand(MODE_FM, 0); break; case RCD_ENABLE_DSTAR: - if (m_dstar != NULL && !m_dstarEnabled) + if (m_dstar != NULL && m_dstarEnabled==false) processEnableCommand(m_dstarEnabled, true); - if (m_dstarNetwork != NULL) - m_dstarNetwork->enable(true); + if (m_dstarNetwork != NULL) + m_dstarNetwork->enable(true); break; case RCD_ENABLE_DMR: - if (m_dmr != NULL && !m_dmrEnabled) + if (m_dmr != NULL && m_dmrEnabled==false) processEnableCommand(m_dmrEnabled, true); - if (m_dmrNetwork != NULL) - m_dmrNetwork->enable(true); + if (m_dmrNetwork != NULL) + m_dmrNetwork->enable(true); break; case RCD_ENABLE_YSF: - if (m_ysf != NULL && !m_ysfEnabled) + if (m_ysf != NULL && m_ysfEnabled==false) processEnableCommand(m_ysfEnabled, true); - if (m_ysfNetwork != NULL) - m_ysfNetwork->enable(true); + if (m_ysfNetwork != NULL) + m_ysfNetwork->enable(true); break; case RCD_ENABLE_P25: - if (m_p25 != NULL && !m_p25Enabled) + if (m_p25 != NULL && m_p25Enabled==false) processEnableCommand(m_p25Enabled, true); - if (m_p25Network != NULL) - m_p25Network->enable(true); + if (m_p25Network != NULL) + m_p25Network->enable(true); break; case RCD_ENABLE_NXDN: - if (m_nxdn != NULL && !m_nxdnEnabled) + if (m_nxdn != NULL && m_nxdnEnabled==false) processEnableCommand(m_nxdnEnabled, true); - 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); + if (m_nxdnNetwork != NULL) + m_nxdnNetwork->enable(true); break; case RCD_ENABLE_FM: - if (!m_fmEnabled) + if (m_fmEnabled==false) processEnableCommand(m_fmEnabled, true); break; - case RCD_ENABLE_AX25: - if (!m_ax25Enabled) - processEnableCommand(m_ax25Enabled, true); - break; case RCD_DISABLE_DSTAR: - if (m_dstar != NULL && m_dstarEnabled) + if (m_dstar != NULL && m_dstarEnabled==true) processEnableCommand(m_dstarEnabled, false); - if (m_dstarNetwork != NULL) - m_dstarNetwork->enable(false); + if (m_dstarNetwork != NULL) + m_dstarNetwork->enable(false); break; case RCD_DISABLE_DMR: - if (m_dmr != NULL && m_dmrEnabled) + if (m_dmr != NULL && m_dmrEnabled==true) processEnableCommand(m_dmrEnabled, false); - if (m_dmrNetwork != NULL) - m_dmrNetwork->enable(false); + if (m_dmrNetwork != NULL) + m_dmrNetwork->enable(false); break; case RCD_DISABLE_YSF: - if (m_ysf != NULL && m_ysfEnabled) + if (m_ysf != NULL && m_ysfEnabled==true) processEnableCommand(m_ysfEnabled, false); - if (m_ysfNetwork != NULL) - m_ysfNetwork->enable(false); + if (m_ysfNetwork != NULL) + m_ysfNetwork->enable(false); break; case RCD_DISABLE_P25: - if (m_p25 != NULL && m_p25Enabled) + if (m_p25 != NULL && m_p25Enabled==true) processEnableCommand(m_p25Enabled, false); - if (m_p25Network != NULL) - m_p25Network->enable(false); + if (m_p25Network != NULL) + m_p25Network->enable(false); break; case RCD_DISABLE_NXDN: - if (m_nxdn != NULL && m_nxdnEnabled) + if (m_nxdn != NULL && m_nxdnEnabled==true) processEnableCommand(m_nxdnEnabled, false); - 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); + if (m_nxdnNetwork != NULL) + m_nxdnNetwork->enable(false); break; case RCD_DISABLE_FM: - if (m_fmEnabled) + if (m_fmEnabled == true) processEnableCommand(m_fmEnabled, false); break; - case RCD_DISABLE_AX25: - if (m_ax25Enabled == true) - processEnableCommand(m_ax25Enabled, false); - break; case RCD_PAGE: if (m_pocsag != NULL) { unsigned int ric = m_remoteControl->getArgUInt(0U); @@ -2511,20 +2074,18 @@ 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); - } - break; + 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); + } default: break; } @@ -2551,10 +2112,8 @@ 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_m17Enabled, m_pocsagEnabled, m_fmEnabled, m_ax25Enabled); + mode=enabled; + m_modem->setModeParams(m_dstarEnabled, m_dmrEnabled, m_ysfEnabled, m_p25Enabled, m_nxdnEnabled, m_pocsagEnabled, m_fmEnabled); if (!m_modem->writeConfig()) LogError("Cannot write Config to MMDVM"); } diff --git a/MMDVMHost.h b/MMDVMHost.h index c69318c..735952a 100644 --- a/MMDVMHost.h +++ b/MMDVMHost.h @@ -23,27 +23,22 @@ #include "POCSAGNetwork.h" #include "POCSAGControl.h" #include "DStarNetwork.h" -#include "AX25Network.h" #include "NXDNNetwork.h" #include "DStarControl.h" -#include "AX25Control.h" #include "DMRControl.h" #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 "FMNetwork.h" #include "DMRLookup.h" -#include "FMControl.h" #include "Display.h" #include "Timer.h" #include "Modem.h" #include "Conf.h" +#include "UMP.h" #include @@ -58,42 +53,33 @@ public: private: CConf m_conf; - IModem* m_modem; + CModem* m_modem; CDStarControl* m_dstar; CDMRControl* m_dmr; CYSFControl* m_ysf; CP25Control* m_p25; CNXDNControl* m_nxdn; - CM17Control* m_m17; CPOCSAGControl* m_pocsag; - CFMControl* m_fm; - CAX25Control* m_ax25; CDStarNetwork* m_dstarNetwork; IDMRNetwork* m_dmrNetwork; CYSFNetwork* m_ysfNetwork; CP25Network* m_p25Network; INXDNNetwork* m_nxdnNetwork; - CM17Network* m_m17Network; CPOCSAGNetwork* m_pocsagNetwork; - CFMNetwork* m_fmNetwork; - CAX25Network* m_ax25Network; CDisplay* m_display; + CUMP* m_ump; unsigned char m_mode; unsigned int m_dstarRFModeHang; unsigned int m_dmrRFModeHang; unsigned int m_ysfRFModeHang; unsigned int m_p25RFModeHang; unsigned int m_nxdnRFModeHang; - unsigned int m_m17RFModeHang; - unsigned int m_fmRFModeHang; 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; - unsigned int m_fmNetModeHang; CTimer m_modeTimer; CTimer m_dmrTXTimer; CTimer m_cwIdTimer; @@ -104,10 +90,8 @@ private: bool m_ysfEnabled; bool m_p25Enabled; bool m_nxdnEnabled; - bool m_m17Enabled; bool m_pocsagEnabled; bool m_fmEnabled; - bool m_ax25Enabled; unsigned int m_cwIdTime; CDMRLookup* m_dmrLookup; CNXDNLookup* m_nxdnLookup; @@ -126,10 +110,7 @@ private: bool createYSFNetwork(); bool createP25Network(); bool createNXDNNetwork(); - bool createM17Network(); bool createPOCSAGNetwork(); - bool createFMNetwork(); - bool createAX25Network(); void remoteControl(); void processModeCommand(unsigned char mode, unsigned int timeout); diff --git a/MMDVMHost.vcxproj b/MMDVMHost.vcxproj index 95d241f..8a7ebe4 100644 --- a/MMDVMHost.vcxproj +++ b/MMDVMHost.vcxproj @@ -153,9 +153,6 @@ - - - @@ -186,22 +183,13 @@ - - - + - - - - - - - @@ -235,7 +223,6 @@ - @@ -244,15 +231,16 @@ - + + @@ -266,8 +254,6 @@ - - @@ -296,20 +282,12 @@ - - - + - - - - - - @@ -341,22 +319,22 @@ - - + + diff --git a/MMDVMHost.vcxproj.filters b/MMDVMHost.vcxproj.filters index d110fc5..b6aa8a8 100644 --- a/MMDVMHost.vcxproj.filters +++ b/MMDVMHost.vcxproj.filters @@ -80,6 +80,9 @@ Header Files + + Header Files + Header Files @@ -212,6 +215,9 @@ Header Files + + Header Files + Header Files @@ -266,6 +272,9 @@ Header Files + + Header Files + Header Files @@ -284,57 +293,12 @@ Header Files - - Header Files - - - Header Files - Header Files Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files @@ -403,6 +367,9 @@ Source Files + + Source Files + Source Files @@ -526,6 +493,9 @@ Source Files + + Source Files + Source Files @@ -574,6 +544,9 @@ Source Files + + Source Files + Source Files @@ -592,51 +565,12 @@ Source Files - - Source Files - - - Source Files - Source Files Source Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - Source Files diff --git a/Makefile b/Makefile index 85a924a..8dc7dfb 100644 --- a/Makefile +++ b/Makefile @@ -2,20 +2,19 @@ CC = cc CXX = c++ -CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -I/usr/local/include -LIBS = -lpthread -lsamplerate -lutil -LDFLAGS = -g -L/usr/local/lib +CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H +LIBS = -lpthread +LDFLAGS = -g OBJECTS = \ - AMBEFEC.o BCH.o AX25Control.o AX25Network.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o MMDVMHost.o \ - Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o NXDNFACCH1.o \ - NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o P25Data.o \ - P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o \ - RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o \ - UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o I2CController.o LCDproc.o Log.o \ + MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ + NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -28,33 +27,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Makefile.Pi b/Makefile.Pi index 2880c34..aa7e3f4 100644 --- a/Makefile.Pi +++ b/Makefile.Pi @@ -3,19 +3,19 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DRASPBERRY_PI -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lsamplerate -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread LDFLAGS = -g -L/usr/local/lib OBJECTS = \ - AMBEFEC.o AX25Control.o AX25Network.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o MMDVMHost.o \ - Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o NXDNFACCH1.o \ - NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o P25Data.o \ - P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o \ - RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o \ - UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o I2CController.o LCDproc.o Log.o \ + MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ + NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o \ + YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -28,33 +28,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Makefile.Pi.Adafruit b/Makefile.Pi.Adafruit index 019b46a..01b7d9f 100644 --- a/Makefile.Pi.Adafruit +++ b/Makefile.Pi.Adafruit @@ -4,19 +4,18 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -DADAFRUIT_DISPLAY -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lsamplerate -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread LDFLAGS = -g -L/usr/local/lib OBJECTS = \ - AMBEFEC.o AX25Control.o AX25Network.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o HD44780.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o \ + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o HD44780.o I2CController.o LCDproc.o Log.o \ MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o \ - RemoteControl.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o \ - Timer.o UDPSocket.o UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -29,33 +28,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Makefile.Pi.HD44780 b/Makefile.Pi.HD44780 index 811a0ac..08458ca 100644 --- a/Makefile.Pi.HD44780 +++ b/Makefile.Pi.HD44780 @@ -3,19 +3,18 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lsamplerate -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread LDFLAGS = -g -L/usr/local/lib OBJECTS = \ - AMBEFEC.o AX25Control.o AX25Network.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o HD44780.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o \ + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o HD44780.o I2CController.o LCDproc.o Log.o \ MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o \ - RemoteControl.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o \ - Timer.o UDPSocket.o UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -28,33 +27,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Makefile.Pi.OLED b/Makefile.Pi.OLED index a8beaec..a58842b 100644 --- a/Makefile.Pi.OLED +++ b/Makefile.Pi.OLED @@ -3,7 +3,7 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DOLED -I/usr/local/include -LIBS = -lArduiPi_OLED -lpthread -lsamplerate -lutil +LIBS = -lArduiPi_OLED -lpthread # If you use NetBSD, add following CFLAGS #CFLAGS += -L/usr/local/lib -Wl,-rpath=/usr/local/lib @@ -11,15 +11,14 @@ LIBS = -lArduiPi_OLED -lpthread -lsamplerate -lutil LDFLAGS = -g -L/usr/local/lib OBJECTS = \ - AMBEFEC.o AX25Control.o AX25Network.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o MMDVMHost.o \ - Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o NXDNFACCH1.o \ - NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o OLED.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o \ - RemoteControl.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o \ - Timer.o UDPSocket.o UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o I2CController.o OLED.o LCDproc.o Log.o \ + MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ + NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -32,33 +31,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Makefile.Pi.PCF8574 b/Makefile.Pi.PCF8574 index 21d4ed0..f9bfc81 100644 --- a/Makefile.Pi.PCF8574 +++ b/Makefile.Pi.PCF8574 @@ -4,19 +4,18 @@ CC = cc CXX = c++ CFLAGS = -g -O3 -Wall -std=c++0x -pthread -DHAVE_LOG_H -DHD44780 -DPCF8574_DISPLAY -I/usr/local/include -LIBS = -lwiringPi -lwiringPiDev -lpthread -lsamplerate -lutil +LIBS = -lwiringPi -lwiringPiDev -lpthread LDFLAGS = -g -L/usr/local/lib OBJECTS = \ - AMBEFEC.o AX25Control.o AX25Network.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o \ - DMRDirectNetwork.o DMREMB.o DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o \ - DMRAccessControl.o DMRTA.o DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o FMControl.o FMNetwork.o Golay2087.o Golay24128.o \ - Hamming.o HD44780.o I2CController.o IIRDirectForm1Filter.o LCDproc.o Log.o M17Control.o M17Convolution.o M17CRC.o M17LICH.o M17Network.o M17Utils.o \ + AMBEFEC.o BCH.o BPTC19696.o CASTInfo.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMRDirectNetwork.o DMREMB.o \ + DMREmbeddedData.o DMRFullLC.o DMRGatewayNetwork.o DMRLookup.o DMRLC.o DMRNetwork.o DMRShortLC.o DMRSlot.o DMRSlotType.o DMRAccessControl.o DMRTA.o \ + DMRTrellis.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o HD44780.o I2CController.o LCDproc.o Log.o \ MMDVMHost.o Modem.o ModemSerialPort.o Mutex.o NetworkInfo.o Nextion.o NullDisplay.o NullModem.o NXDNAudio.o NXDNControl.o NXDNConvolution.o NXDNCRC.o \ NXDNFACCH1.o NXDNIcomNetwork.o NXDNKenwoodNetwork.o NXDNLayer3.o NXDNLICH.o NXDNLookup.o NXDNNetwork.o NXDNSACCH.o NXDNUDCH.o P25Audio.o P25Control.o \ - P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o PseudoTTYController.o POCSAGControl.o POCSAGNetwork.o QR1676.o \ - RemoteControl.o RS129.o RS241213.o RSSIInterpolator.o SerialController.o SerialModem.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSurenoo.o Thread.o \ - Timer.o UDPSocket.o UserDB.o UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o + P25Data.o P25LowSpeedData.o P25Network.o P25NID.o P25Trellis.o P25Utils.o POCSAGControl.o POCSAGNetwork.o QR1676.o RemoteControl.o RS129.o RS241213.o \ + RSSIInterpolator.o SerialController.o SerialPort.o StopWatch.o Sync.o SHA256.o TFTSerial.o TFTSurenoo.o Thread.o Timer.o UDPSocket.o UMP.o UserDB.o \ + UserDBentry.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFNetwork.o YSFPayload.o all: MMDVMHost RemoteCommand @@ -29,33 +28,10 @@ RemoteCommand: Log.o RemoteCommand.o UDPSocket.o %.o: %.cpp $(CXX) $(CFLAGS) -c -o $@ $< -.PHONY install: -install: all +install: install -m 755 MMDVMHost /usr/local/bin/ install -m 755 RemoteCommand /usr/local/bin/ -.PHONY install-service: -install-service: install /etc/MMDVM.ini - @useradd --user-group -M --system mmdvm --shell /bin/false || true - @usermod --groups dialout --append mmdvm || true - @mkdir /var/log/mmdvm || true - @chown mmdvm:mmdvm /var/log/mmdvm - @cp ./linux/systemd/mmdvmhost.service /lib/systemd/system/ - @systemctl enable mmdvmhost.service - -/etc/MMDVM.ini: - @cp -n MMDVM.ini /etc/MMDVM.ini - @sed -i 's/FilePath=./FilePath=\/var\/log\/mmdvm\//' /etc/MMDVM.ini - @sed -i 's/Daemon=0/Daemon=1/' /etc/MMDVM.ini - @chown mmdvm:mmdvm /etc/MMDVM.ini - -.PHONY uninstall-service: -uninstall-service: - @systemctl stop mmdvmhost.service || true - @systemctl disable mmdvmhost.service || true - @rm -f /usr/local/bin/MMDVMHost || true - @rm -f /lib/systemd/system/mmdvmhost.service || true - clean: $(RM) MMDVMHost RemoteCommand *.o *.d *.bak *~ GitVersion.h diff --git a/Modem.cpp b/Modem.cpp index 75c65dc..75aca14 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 by Jonathan Naylor G4KLX + * Copyright (C) 2011-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 @@ -16,8 +16,2151 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "I2CController.h" +#include "DStarDefines.h" +#include "DMRDefines.h" +#include "YSFDefines.h" +#include "P25Defines.h" +#include "NXDNDefines.h" +#include "POCSAGDefines.h" +#include "Thread.h" #include "Modem.h" +#include "NullModem.h" +#include "Utils.h" +#include "Log.h" -IModem::~IModem() +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +const unsigned char MMDVM_FRAME_START = 0xE0U; + +const unsigned char MMDVM_GET_VERSION = 0x00U; +const unsigned char MMDVM_GET_STATUS = 0x01U; +const unsigned char MMDVM_SET_CONFIG = 0x02U; +const unsigned char MMDVM_SET_MODE = 0x03U; +const unsigned char MMDVM_SET_FREQ = 0x04U; + +const unsigned char MMDVM_SEND_CWID = 0x0AU; + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +const unsigned char MMDVM_DMR_DATA1 = 0x18U; +const unsigned char MMDVM_DMR_LOST1 = 0x19U; +const unsigned char MMDVM_DMR_DATA2 = 0x1AU; +const unsigned char MMDVM_DMR_LOST2 = 0x1BU; +const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; +const unsigned char MMDVM_DMR_START = 0x1DU; +const unsigned char MMDVM_DMR_ABORT = 0x1EU; + +const unsigned char MMDVM_YSF_DATA = 0x20U; +const unsigned char MMDVM_YSF_LOST = 0x21U; + +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_POCSAG_DATA = 0x50U; + +const unsigned char MMDVM_FM_PARAMS1 = 0x60U; +const unsigned char MMDVM_FM_PARAMS2 = 0x61U; +const unsigned char MMDVM_FM_PARAMS3 = 0x62U; + +const unsigned char MMDVM_ACK = 0x70U; +const unsigned char MMDVM_NAK = 0x7FU; + +const unsigned char MMDVM_SERIAL = 0x80U; + +const unsigned char MMDVM_TRANSPARENT = 0x90U; +const unsigned char MMDVM_QSO_INFO = 0x91U; + +const unsigned char MMDVM_DEBUG1 = 0xF1U; +const unsigned char MMDVM_DEBUG2 = 0xF2U; +const unsigned char MMDVM_DEBUG3 = 0xF3U; +const unsigned char MMDVM_DEBUG4 = 0xF4U; +const unsigned char MMDVM_DEBUG5 = 0xF5U; + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 2000U; + + +CModem::CModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug) : +m_port(port), +m_dmrColorCode(0U), +m_ysfLoDev(false), +m_ysfTXHang(4U), +m_p25TXHang(5U), +m_nxdnTXHang(5U), +m_duplex(duplex), +m_rxInvert(rxInvert), +m_txInvert(txInvert), +m_pttInvert(pttInvert), +m_txDelay(txDelay), +m_dmrDelay(dmrDelay), +m_rxLevel(0.0F), +m_cwIdTXLevel(0.0F), +m_dstarTXLevel(0.0F), +m_dmrTXLevel(0.0F), +m_ysfTXLevel(0.0F), +m_p25TXLevel(0.0F), +m_nxdnTXLevel(0.0F), +m_pocsagTXLevel(0.0F), +m_fmTXLevel(0.0F), +m_rfLevel(0.0F), +m_useCOSAsLockout(useCOSAsLockout), +m_trace(trace), +m_debug(debug), +m_rxFrequency(0U), +m_txFrequency(0U), +m_pocsagFrequency(0U), +m_dstarEnabled(false), +m_dmrEnabled(false), +m_ysfEnabled(false), +m_p25Enabled(false), +m_nxdnEnabled(false), +m_pocsagEnabled(false), +m_fmEnabled(false), +m_rxDCOffset(0), +m_txDCOffset(0), +m_serial(NULL), +m_buffer(NULL), +m_length(0U), +m_offset(0U), +m_rxDStarData(1000U, "Modem RX D-Star"), +m_txDStarData(1000U, "Modem TX D-Star"), +m_rxDMRData1(1000U, "Modem RX DMR1"), +m_rxDMRData2(1000U, "Modem RX DMR2"), +m_txDMRData1(1000U, "Modem TX DMR1"), +m_txDMRData2(1000U, "Modem TX DMR2"), +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_txPOCSAGData(1000U, "Modem TX POCSAG"), +m_rxTransparentData(1000U, "Modem RX Transparent"), +m_txTransparentData(1000U, "Modem TX Transparent"), +m_sendTransparentDataFrameType(0U), +m_statusTimer(1000U, 0U, 250U), +m_inactivityTimer(1000U, 2U), +m_playoutTimer(1000U, 0U, 10U), +m_dstarSpace(0U), +m_dmrSpace1(0U), +m_dmrSpace2(0U), +m_ysfSpace(0U), +m_p25Space(0U), +m_nxdnSpace(0U), +m_pocsagSpace(0U), +m_tx(false), +m_cd(false), +m_lockout(false), +m_error(false), +m_mode(MODE_IDLE), +m_hwType(HWT_UNKNOWN), +m_fmCallsign(), +m_fmCallsignSpeed(20U), +m_fmCallsignFrequency(1000U), +m_fmCallsignTime(600U), +m_fmCallsignHoldoff(0U), +m_fmCallsignHighLevel(35.0F), +m_fmCallsignLowLevel(15.0F), +m_fmCallsignAtStart(true), +m_fmCallsignAtEnd(true), +m_fmCallsignAtLatch(true), +m_fmRfAck("K"), +m_fmAckSpeed(20U), +m_fmAckFrequency(1750U), +m_fmAckMinTime(4U), +m_fmAckDelay(1000U), +m_fmAckLevel(80.0F), +m_fmTimeout(120U), +m_fmTimeoutLevel(80.0F), +m_fmCtcssFrequency(88.4F), +m_fmCtcssHighThreshold(30U), +m_fmCtcssLowThreshold(20U), +m_fmCtcssLevel(10.0F), +m_fmKerchunkTime(0U), +m_fmHangTime(5U), +m_fmAccessMode(1U), +m_fmCOSInvert(false), +m_fmRFAudioBoost(1U), +m_fmMaxDevLevel(90.0F) { + m_buffer = new unsigned char[BUFFER_LENGTH]; + + assert(!port.empty()); +} + +CModem::~CModem() +{ + delete m_serial; + delete[] m_buffer; +} + +void CModem::setSerialParams(const std::string& protocol, unsigned int address) +{ + // Create the serial controller instance according the protocol specified in conf. + if (protocol == "i2c") + m_serial = new CI2CController(m_port, SERIAL_115200, address, true); + else + m_serial = new CSerialController(m_port, SERIAL_115200, true); +} + +void CModem::setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int txFrequency, int txOffset, int txDCOffset, int rxDCOffset, float rfLevel, unsigned int pocsagFrequency) +{ + m_rxFrequency = rxFrequency + rxOffset; + m_txFrequency = txFrequency + txOffset; + m_txDCOffset = txDCOffset; + m_rxDCOffset = rxDCOffset; + m_rfLevel = rfLevel; + m_pocsagFrequency = pocsagFrequency + txOffset; +} + +void CModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool pocsagEnabled, bool fmEnabled) +{ + m_dstarEnabled = dstarEnabled; + m_dmrEnabled = dmrEnabled; + m_ysfEnabled = ysfEnabled; + m_p25Enabled = p25Enabled; + m_nxdnEnabled = nxdnEnabled; + 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) +{ + m_rxLevel = rxLevel; + m_cwIdTXLevel = cwIdTXLevel; + m_dstarTXLevel = dstarTXLevel; + m_dmrTXLevel = dmrTXLevel; + m_ysfTXLevel = ysfTXLevel; + m_p25TXLevel = p25TXLevel; + m_nxdnTXLevel = nxdnTXLevel; + m_pocsagTXLevel = pocsagTXLevel; + m_fmTXLevel = fmTXLevel; +} + +void CModem::setDMRParams(unsigned int colorCode) +{ + assert(colorCode < 16U); + + m_dmrColorCode = colorCode; +} + +void CModem::setYSFParams(bool loDev, unsigned int txHang) +{ + m_ysfLoDev = loDev; + m_ysfTXHang = txHang; +} + +void CModem::setP25Params(unsigned int txHang) +{ + m_p25TXHang = txHang; +} + +void CModem::setNXDNParams(unsigned int txHang) +{ + m_nxdnTXHang = txHang; +} + +void CModem::setTransparentDataParams(unsigned int sendFrameType) +{ + m_sendTransparentDataFrameType = sendFrameType; +} + +bool CModem::open() +{ + ::LogMessage("Opening the MMDVM"); + + bool ret = m_serial->open(); + if (!ret) + return false; + + ret = readVersion(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } else { + /* Stopping the inactivity timer here when a firmware version has been + successfuly read prevents the death spiral of "no reply from modem..." */ + m_inactivityTimer.stop(); + } + + ret = setFrequency(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } + + ret = setConfig(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } + + if (m_fmEnabled && m_duplex) { + ret = setFMCallsignParams(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } + + ret = setFMAckParams(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } + + ret = setFMMiscParams(); + if (!ret) { + m_serial->close(); + delete m_serial; + m_serial = NULL; + return false; + } + } + + m_statusTimer.start(); + + m_error = false; + m_offset = 0U; + + return true; +} + +void CModem::clock(unsigned int ms) +{ + assert(m_serial != NULL); + + // Poll the modem status every 250ms + m_statusTimer.clock(ms); + if (m_statusTimer.hasExpired()) { + readStatus(); + m_statusTimer.start(); + } + + m_inactivityTimer.clock(ms); + if (m_inactivityTimer.hasExpired()) { + LogError("No reply from the modem for some time, resetting it"); + m_error = true; + close(); + + CThread::sleep(2000U); // 2s + while (!open()) + CThread::sleep(5000U); // 5s + } + + RESP_TYPE_MMDVM type = getResponse(); + + if (type == RTM_TIMEOUT) { + // Nothing to do + } else if (type == RTM_ERROR) { + // Nothing to do + } else { + // type == RTM_OK + switch (m_buffer[2U]) { + case MMDVM_DSTAR_HEADER: { + if (m_trace) + CUtils::dump(1U, "RX D-Star Header", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxDStarData.addData(&data, 1U); + + data = TAG_HEADER; + m_rxDStarData.addData(&data, 1U); + + m_rxDStarData.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_DSTAR_DATA: { + if (m_trace) + CUtils::dump(1U, "RX D-Star Data", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxDStarData.addData(&data, 1U); + + data = TAG_DATA; + m_rxDStarData.addData(&data, 1U); + + m_rxDStarData.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_DSTAR_LOST: { + if (m_trace) + CUtils::dump(1U, "RX D-Star Lost", m_buffer, m_length); + + unsigned char data = 1U; + m_rxDStarData.addData(&data, 1U); + + data = TAG_LOST; + m_rxDStarData.addData(&data, 1U); + } + break; + + case MMDVM_DSTAR_EOT: { + if (m_trace) + CUtils::dump(1U, "RX D-Star EOT", m_buffer, m_length); + + unsigned char data = 1U; + m_rxDStarData.addData(&data, 1U); + + data = TAG_EOT; + m_rxDStarData.addData(&data, 1U); + } + break; + + case MMDVM_DMR_DATA1: { + if (m_trace) + CUtils::dump(1U, "RX DMR Data 1", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxDMRData1.addData(&data, 1U); + + if (m_buffer[3U] == (DMR_SYNC_DATA | DT_TERMINATOR_WITH_LC)) + data = TAG_EOT; + else + data = TAG_DATA; + m_rxDMRData1.addData(&data, 1U); + + m_rxDMRData1.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_DMR_DATA2: { + if (m_trace) + CUtils::dump(1U, "RX DMR Data 2", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxDMRData2.addData(&data, 1U); + + if (m_buffer[3U] == (DMR_SYNC_DATA | DT_TERMINATOR_WITH_LC)) + data = TAG_EOT; + else + data = TAG_DATA; + m_rxDMRData2.addData(&data, 1U); + + m_rxDMRData2.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_DMR_LOST1: { + if (m_trace) + CUtils::dump(1U, "RX DMR Lost 1", m_buffer, m_length); + + unsigned char data = 1U; + m_rxDMRData1.addData(&data, 1U); + + data = TAG_LOST; + m_rxDMRData1.addData(&data, 1U); + } + break; + + case MMDVM_DMR_LOST2: { + if (m_trace) + CUtils::dump(1U, "RX DMR Lost 2", m_buffer, m_length); + + unsigned char data = 1U; + m_rxDMRData2.addData(&data, 1U); + + data = TAG_LOST; + m_rxDMRData2.addData(&data, 1U); + } + break; + + case MMDVM_YSF_DATA: { + if (m_trace) + CUtils::dump(1U, "RX YSF Data", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxYSFData.addData(&data, 1U); + + data = TAG_DATA; + m_rxYSFData.addData(&data, 1U); + + m_rxYSFData.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_YSF_LOST: { + if (m_trace) + CUtils::dump(1U, "RX YSF Lost", m_buffer, m_length); + + unsigned char data = 1U; + m_rxYSFData.addData(&data, 1U); + + data = TAG_LOST; + m_rxYSFData.addData(&data, 1U); + } + break; + + case MMDVM_P25_HDR: { + if (m_trace) + CUtils::dump(1U, "RX P25 Header", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxP25Data.addData(&data, 1U); + + data = TAG_HEADER; + m_rxP25Data.addData(&data, 1U); + + m_rxP25Data.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_P25_LDU: { + if (m_trace) + CUtils::dump(1U, "RX P25 LDU", m_buffer, m_length); + + unsigned char data = m_length - 2U; + m_rxP25Data.addData(&data, 1U); + + data = TAG_DATA; + m_rxP25Data.addData(&data, 1U); + + m_rxP25Data.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case MMDVM_P25_LOST: { + if (m_trace) + CUtils::dump(1U, "RX P25 Lost", m_buffer, m_length); + + unsigned char data = 1U; + m_rxP25Data.addData(&data, 1U); + + data = TAG_LOST; + m_rxP25Data.addData(&data, 1U); + } + 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); + + m_p25Space = 0U; + m_nxdnSpace = 0U; + m_pocsagSpace = 0U; + + m_mode = m_buffer[4U]; + + m_tx = (m_buffer[5U] & 0x01U) == 0x01U; + + bool adcOverflow = (m_buffer[5U] & 0x02U) == 0x02U; + if (adcOverflow) + LogError("MMDVM ADC levels have overflowed"); + + bool rxOverflow = (m_buffer[5U] & 0x04U) == 0x04U; + if (rxOverflow) + LogError("MMDVM RX buffer has overflowed"); + + bool txOverflow = (m_buffer[5U] & 0x08U) == 0x08U; + if (txOverflow) + LogError("MMDVM TX buffer has overflowed"); + + m_lockout = (m_buffer[5U] & 0x10U) == 0x10U; + + bool dacOverflow = (m_buffer[5U] & 0x20U) == 0x20U; + if (dacOverflow) + LogError("MMDVM DAC levels have overflowed"); + + m_cd = (m_buffer[5U] & 0x40U) == 0x40U; + + m_dstarSpace = m_buffer[6U]; + m_dmrSpace1 = m_buffer[7U]; + m_dmrSpace2 = m_buffer[8U]; + m_ysfSpace = m_buffer[9U]; + + if (m_length > 10U) + m_p25Space = m_buffer[10U]; + if (m_length > 11U) + m_nxdnSpace = m_buffer[11U]; + if (m_length > 12U) + m_pocsagSpace = m_buffer[12U]; + + 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)); + } + break; + + case MMDVM_TRANSPARENT: { + if (m_trace) + CUtils::dump(1U, "RX Transparent Data", m_buffer, m_length); + + unsigned char offset = m_sendTransparentDataFrameType; + if (offset > 1U) offset = 1U; + unsigned char data = m_length - 3U + offset; + m_rxTransparentData.addData(&data, 1U); + + m_rxTransparentData.addData(m_buffer + 3U - offset, m_length - 3U + offset); + } + break; + + // These should not be received, but don't complain if we do + case MMDVM_GET_VERSION: + case MMDVM_ACK: + break; + + case MMDVM_NAK: + LogWarning("Received a NAK from the MMDVM, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); + break; + + case MMDVM_DEBUG1: + case MMDVM_DEBUG2: + case MMDVM_DEBUG3: + case MMDVM_DEBUG4: + case MMDVM_DEBUG5: + printDebug(); + break; + + case MMDVM_SERIAL: + //MMDVMHost does not process serial data from the display, + // so we send it to the transparent port if sendFrameType==1 + if (m_sendTransparentDataFrameType > 0U) { + if (m_trace) + CUtils::dump(1U, "RX Serial Data", m_buffer, m_length); + + unsigned char offset = m_sendTransparentDataFrameType; + if (offset > 1U) offset = 1U; + unsigned char data = m_length - 3U + offset; + m_rxTransparentData.addData(&data, 1U); + + m_rxTransparentData.addData(m_buffer + 3U - offset, m_length - 3U + offset); + break; //only break when sendFrameType>0, else message is unknown + } + default: + LogMessage("Unknown message, type: %02X", m_buffer[2U]); + CUtils::dump("Buffer dump", m_buffer, m_length); + break; + } + } + + // Only feed data to the modem if the playout timer has expired + m_playoutTimer.clock(ms); + if (!m_playoutTimer.hasExpired()) + return; + + if (m_dstarSpace > 1U && !m_txDStarData.isEmpty()) { + unsigned char buffer[4U]; + m_txDStarData.peek(buffer, 4U); + + if ((buffer[3U] == MMDVM_DSTAR_HEADER && m_dstarSpace > 4U) || + (buffer[3U] == MMDVM_DSTAR_DATA && m_dstarSpace > 1U) || + (buffer[3U] == MMDVM_DSTAR_EOT && m_dstarSpace > 1U)) { + unsigned char len = 0U; + m_txDStarData.getData(&len, 1U); + m_txDStarData.getData(m_buffer, len); + + switch (buffer[3U]) { + case MMDVM_DSTAR_HEADER: + if (m_trace) + CUtils::dump(1U, "TX D-Star Header", m_buffer, len); + m_dstarSpace -= 4U; + break; + case MMDVM_DSTAR_DATA: + if (m_trace) + CUtils::dump(1U, "TX D-Star Data", m_buffer, len); + m_dstarSpace -= 1U; + break; + default: + if (m_trace) + CUtils::dump(1U, "TX D-Star EOT", m_buffer, len); + m_dstarSpace -= 1U; + break; + } + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing D-Star data to the MMDVM"); + + m_playoutTimer.start(); + } + } + + if (m_dmrSpace1 > 1U && !m_txDMRData1.isEmpty()) { + unsigned char len = 0U; + m_txDMRData1.getData(&len, 1U); + m_txDMRData1.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX DMR Data 1", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing DMR data to the MMDVM"); + + m_playoutTimer.start(); + + m_dmrSpace1--; + } + + if (m_dmrSpace2 > 1U && !m_txDMRData2.isEmpty()) { + unsigned char len = 0U; + m_txDMRData2.getData(&len, 1U); + m_txDMRData2.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX DMR Data 2", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing DMR data to the MMDVM"); + + m_playoutTimer.start(); + + m_dmrSpace2--; + } + + if (m_ysfSpace > 1U && !m_txYSFData.isEmpty()) { + unsigned char len = 0U; + m_txYSFData.getData(&len, 1U); + m_txYSFData.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX YSF Data", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing YSF data to the MMDVM"); + + m_playoutTimer.start(); + + m_ysfSpace--; + } + + if (m_p25Space > 1U && !m_txP25Data.isEmpty()) { + unsigned char len = 0U; + m_txP25Data.getData(&len, 1U); + m_txP25Data.getData(m_buffer, len); + + if (m_trace) { + if (m_buffer[2U] == MMDVM_P25_HDR) + CUtils::dump(1U, "TX P25 HDR", m_buffer, len); + else + CUtils::dump(1U, "TX P25 LDU", m_buffer, len); + } + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing P25 data to the MMDVM"); + + m_playoutTimer.start(); + + 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--; + } + + if (m_pocsagSpace > 1U && !m_txPOCSAGData.isEmpty()) { + unsigned char len = 0U; + m_txPOCSAGData.getData(&len, 1U); + m_txPOCSAGData.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX POCSAG Data", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing POCSAG data to the MMDVM"); + + m_playoutTimer.start(); + + m_pocsagSpace--; + } + + if (!m_txTransparentData.isEmpty()) { + unsigned char len = 0U; + m_txTransparentData.getData(&len, 1U); + m_txTransparentData.getData(m_buffer, len); + + if (m_trace) + CUtils::dump(1U, "TX Transparent Data", m_buffer, len); + + int ret = m_serial->write(m_buffer, len); + if (ret != int(len)) + LogWarning("Error when writing Transparent data to the MMDVM"); + } +} + +void CModem::close() +{ + assert(m_serial != NULL); + + ::LogMessage("Closing the MMDVM"); + + m_serial->close(); +} + +unsigned int CModem::readDStarData(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxDStarData.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxDStarData.getData(&len, 1U); + m_rxDStarData.getData(data, len); + + return len; +} + +unsigned int CModem::readDMRData1(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxDMRData1.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxDMRData1.getData(&len, 1U); + m_rxDMRData1.getData(data, len); + + return len; +} + +unsigned int CModem::readDMRData2(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxDMRData2.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxDMRData2.getData(&len, 1U); + m_rxDMRData2.getData(data, len); + + return len; +} + +unsigned int CModem::readYSFData(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxYSFData.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxYSFData.getData(&len, 1U); + m_rxYSFData.getData(data, len); + + return len; +} + +unsigned int CModem::readP25Data(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxP25Data.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxP25Data.getData(&len, 1U); + m_rxP25Data.getData(data, len); + + 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; +} + +unsigned int CModem::readTransparentData(unsigned char* data) +{ + assert(data != NULL); + + if (m_rxTransparentData.isEmpty()) + return 0U; + + unsigned char len = 0U; + m_rxTransparentData.getData(&len, 1U); + m_rxTransparentData.getData(data, len); + + return len; +} + +// To be implemented later if needed +unsigned int CModem::readSerial(unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + return 0U; +} + +bool CModem::hasDStarSpace() const +{ + unsigned int space = m_txDStarData.freeSpace() / (DSTAR_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeDStarData(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + unsigned char buffer[50U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 2U; + + switch (data[0U]) { + case TAG_HEADER: + buffer[2U] = MMDVM_DSTAR_HEADER; + break; + case TAG_DATA: + buffer[2U] = MMDVM_DSTAR_DATA; + break; + case TAG_EOT: + buffer[2U] = MMDVM_DSTAR_EOT; + break; + default: + CUtils::dump(2U, "Unknown D-Star packet type", data, length); + return false; + } + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txDStarData.addData(&len, 1U); + m_txDStarData.addData(buffer, len); + + return true; +} + +bool CModem::hasDMRSpace1() const +{ + unsigned int space = m_txDMRData1.freeSpace() / (DMR_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::hasDMRSpace2() const +{ + unsigned int space = m_txDMRData2.freeSpace() / (DMR_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeDMRData1(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[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = MMDVM_DMR_DATA1; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txDMRData1.addData(&len, 1U); + m_txDMRData1.addData(buffer, len); + + return true; +} + +bool CModem::writeDMRData2(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[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = MMDVM_DMR_DATA2; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txDMRData2.addData(&len, 1U); + m_txDMRData2.addData(buffer, len); + + return true; +} + +bool CModem::hasYSFSpace() const +{ + unsigned int space = m_txYSFData.freeSpace() / (YSF_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeYSFData(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_YSF_DATA; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txYSFData.addData(&len, 1U); + m_txYSFData.addData(buffer, len); + + return true; +} + +bool CModem::hasP25Space() const +{ + unsigned int space = m_txP25Data.freeSpace() / (P25_LDU_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writeP25Data(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + if (data[0U] != TAG_HEADER && data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + + unsigned char buffer[250U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = (data[0U] == TAG_HEADER) ? MMDVM_P25_HDR : MMDVM_P25_LDU; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + unsigned char len = length + 2U; + m_txP25Data.addData(&len, 1U); + m_txP25Data.addData(buffer, len); + + 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::hasPOCSAGSpace() const +{ + unsigned int space = m_txPOCSAGData.freeSpace() / (POCSAG_FRAME_LENGTH_BYTES + 4U); + + return space > 1U; +} + +bool CModem::writePOCSAGData(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + unsigned char buffer[130U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = MMDVM_POCSAG_DATA; + + ::memcpy(buffer + 3U, data, length); + + unsigned char len = length + 3U; + m_txPOCSAGData.addData(&len, 1U); + m_txPOCSAGData.addData(buffer, len); + + return true; +} + +bool CModem::writeTransparentData(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + unsigned char buffer[250U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = MMDVM_TRANSPARENT; + + if (m_sendTransparentDataFrameType > 0U) { + ::memcpy(buffer + 2U, data, length); + length--; + buffer[1U]--; + + //when sendFrameType==1 , only 0x80 and 0x90 (MMDVM_SERIAL and MMDVM_TRANSPARENT) are allowed + // and reverted to default (MMDVM_TRANSPARENT) for any other value + //when >1, frame type is not checked + if (m_sendTransparentDataFrameType == 1U) { + if ((buffer[2U] & 0xE0) != 0x80) + buffer[2U] = MMDVM_TRANSPARENT; + } + } else { + ::memcpy(buffer + 3U, data, length); + } + + unsigned char len = length + 3U; + m_txTransparentData.addData(&len, 1U); + m_txTransparentData.addData(buffer, len); + + return true; +} + +bool CModem::writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) +{ + assert(m_serial != NULL); + assert(my1 != NULL); + assert(my2 != NULL); + assert(your != NULL); + assert(type != NULL); + assert(reflector != NULL); + + unsigned char buffer[50U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 33U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_DSTAR; + + ::memcpy(buffer + 4U, my1, DSTAR_LONG_CALLSIGN_LENGTH); + ::memcpy(buffer + 12U, my2, DSTAR_SHORT_CALLSIGN_LENGTH); + + ::memcpy(buffer + 16U, your, DSTAR_LONG_CALLSIGN_LENGTH); + + ::memcpy(buffer + 24U, type, 1U); + + ::memcpy(buffer + 25U, reflector, DSTAR_LONG_CALLSIGN_LENGTH); + + return m_serial->write(buffer, 33U) != 33; +} + +bool CModem::writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dest, const char* type) +{ + assert(m_serial != NULL); + assert(type != NULL); + + unsigned char buffer[50U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 47U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_DMR; + + buffer[4U] = slotNo; + + ::sprintf((char*)(buffer + 5U), "%20.20s", src.c_str()); + + buffer[25U] = group ? 'G' : 'I'; + + ::sprintf((char*)(buffer + 26U), "%20.20s", dest.c_str()); + + ::memcpy(buffer + 46U, type, 1U); + + return m_serial->write(buffer, 47U) != 47; +} + +bool CModem::writeYSFInfo(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin) +{ + assert(m_serial != NULL); + assert(source != NULL); + assert(dest != NULL); + assert(type != NULL); + assert(origin != NULL); + + unsigned char buffer[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 36U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_YSF; + + ::memcpy(buffer + 4U, source, YSF_CALLSIGN_LENGTH); + ::memcpy(buffer + 14U, dest, YSF_CALLSIGN_LENGTH); + + ::memcpy(buffer + 24U, type, 1U); + + ::memcpy(buffer + 25U, origin, YSF_CALLSIGN_LENGTH); + + buffer[35U] = dgid; + + return m_serial->write(buffer, 36U) != 36; +} + +bool CModem::writeP25Info(const char* source, bool group, unsigned int dest, const char* type) +{ + assert(m_serial != NULL); + assert(source != NULL); + assert(type != NULL); + + unsigned char buffer[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 31U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_DMR; + + ::sprintf((char*)(buffer + 4U), "%20.20s", source); + + buffer[24U] = group ? 'G' : 'I'; + + ::sprintf((char*)(buffer + 25U), "%05u", dest); // 16-bits + + ::memcpy(buffer + 30U, type, 1U); + + return m_serial->write(buffer, 31U) != 31; +} + +bool CModem::writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type) +{ + assert(m_serial != NULL); + assert(source != NULL); + assert(type != NULL); + + unsigned char buffer[40U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 31U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_NXDN; + + ::sprintf((char*)(buffer + 4U), "%20.20s", source); + + buffer[24U] = group ? 'G' : 'I'; + + ::sprintf((char*)(buffer + 25U), "%05u", dest); // 16-bits + + ::memcpy(buffer + 30U, type, 1U); + + return m_serial->write(buffer, 31U) != 31; +} + +bool CModem::writePOCSAGInfo(unsigned int ric, const std::string& message) +{ + assert(m_serial != NULL); + + size_t length = message.size(); + + unsigned char buffer[250U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 11U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = MODE_POCSAG; + + ::sprintf((char*)(buffer + 4U), "%07u", ric); // 21-bits + + ::memcpy(buffer + 11U, message.c_str(), length); + + int ret = m_serial->write(buffer, length + 11U); + + return ret != int(length + 11U); +} + +bool CModem::writeIPInfo(const std::string& address) +{ + assert(m_serial != NULL); + + size_t length = address.size(); + + unsigned char buffer[25U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 4U; + buffer[2U] = MMDVM_QSO_INFO; + + buffer[3U] = 250U; + + ::memcpy(buffer + 4U, address.c_str(), length); + + int ret = m_serial->write(buffer, length + 4U); + + return ret != int(length + 4U); +} + +bool CModem::writeSerial(const unsigned char* data, unsigned int length) +{ + assert(m_serial != NULL); + assert(data != NULL); + assert(length > 0U); + + unsigned char buffer[250U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = MMDVM_SERIAL; + + ::memcpy(buffer + 3U, data, length); + + int ret = m_serial->write(buffer, length + 3U); + + return ret != int(length + 3U); +} + +bool CModem::hasTX() const +{ + return m_tx; +} + +bool CModem::hasCD() const +{ + return m_cd; +} + +bool CModem::hasLockout() const +{ + return m_lockout; +} + +bool CModem::hasError() const +{ + return m_error; +} + +bool CModem::readVersion() +{ + assert(m_serial != NULL); + + CThread::sleep(2000U); // 2s + + for (unsigned int i = 0U; i < 6U; i++) { + unsigned char buffer[3U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = MMDVM_GET_VERSION; + + // CUtils::dump(1U, "Written", buffer, 3U); + + int ret = m_serial->write(buffer, 3U); + if (ret != 3) + return false; + +#if defined(__APPLE__) + m_serial->setNonblock(true); +#endif + + for (unsigned int count = 0U; count < MAX_RESPONSES; count++) { + CThread::sleep(10U); + RESP_TYPE_MMDVM resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] == MMDVM_GET_VERSION) { + if (::memcmp(m_buffer + 4U, "MMDVM ", 6U) == 0) + m_hwType = HWT_MMDVM; + else if (::memcmp(m_buffer + 4U, "DVMEGA", 6U) == 0) + m_hwType = HWT_DVMEGA; + else if (::memcmp(m_buffer + 4U, "ZUMspot", 7U) == 0) + m_hwType = HWT_MMDVM_ZUMSPOT; + else if (::memcmp(m_buffer + 4U, "MMDVM_HS_Hat", 12U) == 0) + m_hwType = HWT_MMDVM_HS_HAT; + else if (::memcmp(m_buffer + 4U, "MMDVM_HS_Dual_Hat", 17U) == 0) + m_hwType = HWT_MMDVM_HS_DUAL_HAT; + else if (::memcmp(m_buffer + 4U, "Nano_hotSPOT", 12U) == 0) + m_hwType = HWT_NANO_HOTSPOT; + else if (::memcmp(m_buffer + 4U, "Nano_DV", 7U) == 0) + m_hwType = HWT_NANO_DV; + else if (::memcmp(m_buffer + 4U, "D2RG_MMDVM_HS", 13U) == 0) + m_hwType = HWT_D2RG_MMDVM_HS; + else if (::memcmp(m_buffer + 4U, "MMDVM_HS-", 9U) == 0) + m_hwType = HWT_MMDVM_HS; + else if (::memcmp(m_buffer + 4U, "OpenGD77_HS", 11U) == 0) + m_hwType = HWT_OPENGD77_HS; + else if (::memcmp(m_buffer + 4U, "SkyBridge", 9U) == 0) + m_hwType = HWT_SKYBRIDGE; + + LogInfo("MMDVM protocol version: %u, description: %.*s", m_buffer[3U], m_length - 4U, m_buffer + 4U); + return true; + } + } + + CThread::sleep(1500U); + } + + LogError("Unable to read the firmware version after six attempts"); + + return false; +} + +bool CModem::readStatus() +{ + assert(m_serial != NULL); + + unsigned char buffer[3U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = MMDVM_GET_STATUS; + + // CUtils::dump(1U, "Written", buffer, 3U); + + return m_serial->write(buffer, 3U) == 3; +} + +bool CModem::writeConfig() +{ + return setConfig(); +} + +bool CModem::setConfig() +{ + assert(m_serial != NULL); + + unsigned char buffer[30U]; + + buffer[0U] = MMDVM_FRAME_START; + + buffer[1U] = 24U; + + buffer[2U] = MMDVM_SET_CONFIG; + + buffer[3U] = 0x00U; + if (m_rxInvert) + buffer[3U] |= 0x01U; + if (m_txInvert) + buffer[3U] |= 0x02U; + if (m_pttInvert) + buffer[3U] |= 0x04U; + if (m_ysfLoDev) + buffer[3U] |= 0x08U; + if (m_debug) + buffer[3U] |= 0x10U; + if (m_useCOSAsLockout) + buffer[3U] |= 0x20U; + if (!m_duplex) + buffer[3U] |= 0x80U; + + buffer[4U] = 0x00U; + if (m_dstarEnabled) + buffer[4U] |= 0x01U; + if (m_dmrEnabled) + buffer[4U] |= 0x02U; + if (m_ysfEnabled) + buffer[4U] |= 0x04U; + if (m_p25Enabled) + buffer[4U] |= 0x08U; + if (m_nxdnEnabled) + buffer[4U] |= 0x10U; + if (m_pocsagEnabled) + buffer[4U] |= 0x20U; + if (m_fmEnabled && m_duplex) + buffer[4U] |= 0x40U; + + buffer[5U] = m_txDelay / 10U; // In 10ms units + + buffer[6U] = MODE_IDLE; + + buffer[7U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F); + + buffer[8U] = (unsigned char)(m_cwIdTXLevel * 2.55F + 0.5F); + + buffer[9U] = m_dmrColorCode; + + buffer[10U] = m_dmrDelay; + + buffer[11U] = 128U; // Was OscOffset + + buffer[12U] = (unsigned char)(m_dstarTXLevel * 2.55F + 0.5F); + buffer[13U] = (unsigned char)(m_dmrTXLevel * 2.55F + 0.5F); + buffer[14U] = (unsigned char)(m_ysfTXLevel * 2.55F + 0.5F); + buffer[15U] = (unsigned char)(m_p25TXLevel * 2.55F + 0.5F); + + buffer[16U] = (unsigned char)(m_txDCOffset + 128); + buffer[17U] = (unsigned char)(m_rxDCOffset + 128); + + buffer[18U] = (unsigned char)(m_nxdnTXLevel * 2.55F + 0.5F); + + buffer[19U] = (unsigned char)m_ysfTXHang; + + buffer[20U] = (unsigned char)(m_pocsagTXLevel * 2.55F + 0.5F); + + buffer[21U] = (unsigned char)(m_fmTXLevel * 2.55F + 0.5F); + + buffer[22U] = (unsigned char)m_p25TXHang; + + buffer[23U] = (unsigned char)m_nxdnTXHang; + + // CUtils::dump(1U, "Written", buffer, 24U); + + int ret = m_serial->write(buffer, 24U); + if (ret != 24) + return false; + + unsigned int count = 0U; + RESP_TYPE_MMDVM resp; + do { + CThread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError("The MMDVM is not responding to the SET_CONFIG command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); + + // CUtils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { + LogError("Received a NAK to the SET_CONFIG command from the modem"); + return false; + } + + m_playoutTimer.start(); + + return true; +} + +bool CModem::setFrequency() +{ + assert(m_serial != NULL); + + unsigned char buffer[20U]; + unsigned char len; + unsigned int pocsagFrequency = 433000000U; + + if (m_pocsagEnabled) + pocsagFrequency = m_pocsagFrequency; + + if (m_hwType == HWT_DVMEGA) + len = 12U; + else { + buffer[12U] = (unsigned char)(m_rfLevel * 2.55F + 0.5F); + + buffer[13U] = (pocsagFrequency >> 0) & 0xFFU; + buffer[14U] = (pocsagFrequency >> 8) & 0xFFU; + buffer[15U] = (pocsagFrequency >> 16) & 0xFFU; + buffer[16U] = (pocsagFrequency >> 24) & 0xFFU; + + len = 17U; + } + + buffer[0U] = MMDVM_FRAME_START; + + buffer[1U] = len; + + buffer[2U] = MMDVM_SET_FREQ; + + buffer[3U] = 0x00U; + + buffer[4U] = (m_rxFrequency >> 0) & 0xFFU; + buffer[5U] = (m_rxFrequency >> 8) & 0xFFU; + buffer[6U] = (m_rxFrequency >> 16) & 0xFFU; + buffer[7U] = (m_rxFrequency >> 24) & 0xFFU; + + buffer[8U] = (m_txFrequency >> 0) & 0xFFU; + buffer[9U] = (m_txFrequency >> 8) & 0xFFU; + buffer[10U] = (m_txFrequency >> 16) & 0xFFU; + buffer[11U] = (m_txFrequency >> 24) & 0xFFU; + + // CUtils::dump(1U, "Written", buffer, len); + + int ret = m_serial->write(buffer, len); + if (ret != len) + return false; + + unsigned int count = 0U; + RESP_TYPE_MMDVM resp; + do { + CThread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError("The MMDVM is not responding to the SET_FREQ command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); + + // CUtils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { + LogError("Received a NAK to the SET_FREQ command from the modem"); + return false; + } + + return true; +} + +RESP_TYPE_MMDVM CModem::getResponse() +{ + assert(m_serial != NULL); + + if (m_offset == 0U) { + // Get the start of the frame or nothing at all + int ret = m_serial->read(m_buffer + 0U, 1U); + if (ret < 0) { + LogError("Error when reading from the modem"); + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (m_buffer[0U] != MMDVM_FRAME_START) + return RTM_TIMEOUT; + + m_offset = 1U; + } + + if (m_offset == 1U) { + // Get the length of the frame + int ret = m_serial->read(m_buffer + 1U, 1U); + if (ret < 0) { + LogError("Error when reading from the modem"); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (m_buffer[1U] >= 250U) { + LogError("Invalid length received from the modem - %u", m_buffer[1U]); + m_offset = 0U; + return RTM_ERROR; + } + + m_length = m_buffer[1U]; + m_offset = 2U; + } + + if (m_offset == 2U) { + // Get the frame type + int ret = m_serial->read(m_buffer + 2U, 1U); + if (ret < 0) { + LogError("Error when reading from the modem"); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + m_offset = 3U; + } + + if (m_offset >= 3U) { + // Use later two byte length field + if (m_length == 0U) { + int ret = m_serial->read(m_buffer + 3U, 2U); + if (ret < 0) { + LogError("Error when reading from the modem"); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + m_length = (m_buffer[3U] << 8) | m_buffer[4U]; + m_offset = 5U; + } + + while (m_offset < m_length) { + int ret = m_serial->read(m_buffer + m_offset, m_length - m_offset); + if (ret < 0) { + LogError("Error when reading from the modem"); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (ret > 0) + m_offset += ret; + } + } + + m_offset = 0U; + + // CUtils::dump(1U, "Received", m_buffer, m_length); + + return RTM_OK; +} + +HW_TYPE CModem::getHWType() const +{ + return m_hwType; +} + +unsigned char CModem::getMode() const +{ + return m_mode; +} + +bool CModem::setMode(unsigned char mode) +{ + assert(m_serial != NULL); + + unsigned char buffer[4U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = MMDVM_SET_MODE; + buffer[3U] = mode; + + // CUtils::dump(1U, "Written", buffer, 4U); + + return m_serial->write(buffer, 4U) == 4; +} + +bool CModem::sendCWId(const std::string& callsign) +{ + assert(m_serial != NULL); + + unsigned int length = callsign.length(); + if (length > 200U) + length = 200U; + + unsigned char buffer[205U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = MMDVM_SEND_CWID; + + for (unsigned int i = 0U; i < length; i++) + buffer[i + 3U] = callsign.at(i); + + // CUtils::dump(1U, "Written", buffer, length + 3U); + + return m_serial->write(buffer, length + 3U) == int(length + 3U); +} + +bool CModem::writeDMRStart(bool tx) +{ + assert(m_serial != NULL); + + if (tx && m_tx) + return true; + if (!tx && !m_tx) + return true; + + unsigned char buffer[4U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = MMDVM_DMR_START; + buffer[3U] = tx ? 0x01U : 0x00U; + + // CUtils::dump(1U, "Written", buffer, 4U); + + return m_serial->write(buffer, 4U) == 4; +} + +bool CModem::writeDMRAbort(unsigned int slotNo) +{ + assert(m_serial != NULL); + + if (slotNo == 1U) + m_txDMRData1.clear(); + else + m_txDMRData2.clear(); + + unsigned char buffer[4U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = MMDVM_DMR_ABORT; + buffer[3U] = slotNo; + + // CUtils::dump(1U, "Written", buffer, 4U); + + return m_serial->write(buffer, 4U) == 4; +} + +bool CModem::writeDMRShortLC(const unsigned char* lc) +{ + assert(m_serial != NULL); + assert(lc != NULL); + + unsigned char buffer[12U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 12U; + buffer[2U] = MMDVM_DMR_SHORTLC; + buffer[3U] = lc[0U]; + buffer[4U] = lc[1U]; + buffer[5U] = lc[2U]; + buffer[6U] = lc[3U]; + buffer[7U] = lc[4U]; + buffer[8U] = lc[5U]; + buffer[9U] = lc[6U]; + buffer[10U] = lc[7U]; + buffer[11U] = lc[8U]; + + // CUtils::dump(1U, "Written", buffer, 12U); + + return m_serial->write(buffer, 12U) == 12; +} + +void CModem::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) +{ + m_fmCallsign = callsign; + m_fmCallsignSpeed = callsignSpeed; + m_fmCallsignFrequency = callsignFrequency; + m_fmCallsignTime = callsignTime; + m_fmCallsignHoldoff = callsignHoldoff; + m_fmCallsignHighLevel = callsignHighLevel; + m_fmCallsignLowLevel = callsignLowLevel; + m_fmCallsignAtStart = callsignAtStart; + m_fmCallsignAtEnd = callsignAtEnd; + m_fmCallsignAtLatch = callsignAtLatch; +} + +void CModem::setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel) +{ + m_fmRfAck = rfAck; + m_fmAckSpeed = ackSpeed; + m_fmAckFrequency = ackFrequency; + m_fmAckMinTime = ackMinTime; + m_fmAckDelay = ackDelay; + m_fmAckLevel = ackLevel; +} + +void CModem::setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, unsigned int rfAudioBoost, float maxDevLevel) +{ + m_fmTimeout = timeout; + m_fmTimeoutLevel = timeoutLevel; + + m_fmCtcssFrequency = ctcssFrequency; + m_fmCtcssHighThreshold = ctcssHighThreshold; + m_fmCtcssLowThreshold = ctcssLowThreshold; + m_fmCtcssLevel = ctcssLevel; + + m_fmKerchunkTime = kerchunkTime; + m_fmHangTime = hangTime; + + m_fmAccessMode = accessMode; + m_fmCOSInvert = cosInvert; + + m_fmRFAudioBoost = rfAudioBoost; + m_fmMaxDevLevel = maxDevLevel; +} + +bool CModem::setFMCallsignParams() +{ + assert(m_serial != NULL); + + unsigned char buffer[80U]; + unsigned char len = 10U + m_fmCallsign.size(); + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = len; + buffer[2U] = MMDVM_FM_PARAMS1; + + buffer[3U] = m_fmCallsignSpeed; + buffer[4U] = m_fmCallsignFrequency / 10U; + buffer[5U] = m_fmCallsignTime; + buffer[6U] = m_fmCallsignHoldoff; + + buffer[7U] = (unsigned char)(m_fmCallsignHighLevel * 2.55F + 0.5F); + buffer[8U] = (unsigned char)(m_fmCallsignLowLevel * 2.55F + 0.5F); + + buffer[9U] = 0x00U; + if (m_fmCallsignAtStart) + buffer[9U] |= 0x01U; + if (m_fmCallsignAtEnd) + buffer[9U] |= 0x02U; + if (m_fmCallsignAtLatch) + buffer[9U] |= 0x04U; + + for (unsigned int i = 0U; i < m_fmCallsign.size(); i++) + buffer[10U + i] = m_fmCallsign.at(i); + + // CUtils::dump(1U, "Written", buffer, len); + + int ret = m_serial->write(buffer, len); + if (ret != len) + return false; + + unsigned int count = 0U; + RESP_TYPE_MMDVM resp; + do { + CThread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError("The MMDVM is not responding to the SET_FM_PARAMS1 command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); + + // CUtils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { + LogError("Received a NAK to the SET_FM_PARAMS1 command from the modem"); + return false; + } + + return true; +} + +bool CModem::setFMAckParams() +{ + assert(m_serial != NULL); + + unsigned char buffer[80U]; + unsigned char len = 8U + m_fmRfAck.size(); + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = len; + buffer[2U] = MMDVM_FM_PARAMS2; + + buffer[3U] = m_fmAckSpeed; + buffer[4U] = m_fmAckFrequency / 10U; + buffer[5U] = m_fmAckMinTime; + buffer[6U] = m_fmAckDelay / 10U; + + buffer[7U] = (unsigned char)(m_fmAckLevel * 2.55F + 0.5F); + + for (unsigned int i = 0U; i < m_fmRfAck.size(); i++) + buffer[8U + i] = m_fmRfAck.at(i); + + // CUtils::dump(1U, "Written", buffer, len); + + int ret = m_serial->write(buffer, len); + if (ret != len) + return false; + + unsigned int count = 0U; + RESP_TYPE_MMDVM resp; + do { + CThread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError("The MMDVM is not responding to the SET_FM_PARAMS2 command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); + + // CUtils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { + LogError("Received a NAK to the SET_FM_PARAMS2 command from the modem"); + return false; + } + + return true; +} + +bool CModem::setFMMiscParams() +{ + assert(m_serial != NULL); + + unsigned char buffer[20U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 15U; + buffer[2U] = MMDVM_FM_PARAMS3; + + buffer[3U] = m_fmTimeout / 5U; + buffer[4U] = (unsigned char)(m_fmTimeoutLevel * 2.55F + 0.5F); + + buffer[5U] = (unsigned char)m_fmCtcssFrequency; + buffer[6U] = m_fmCtcssHighThreshold; + buffer[7U] = m_fmCtcssLowThreshold; + buffer[8U] = (unsigned char)(m_fmCtcssLevel * 2.55F + 0.5F); + + buffer[9U] = m_fmKerchunkTime; + buffer[10U] = m_fmHangTime; + + buffer[11U] = m_fmAccessMode; + if (m_fmCOSInvert) + buffer[11U] |= 0x80U; + + buffer[12U] = m_fmRFAudioBoost; + + buffer[13U] = (unsigned char)(m_fmMaxDevLevel * 2.55F + 0.5F); + + buffer[14U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F); + + // CUtils::dump(1U, "Written", buffer, 15U); + + int ret = m_serial->write(buffer, 15U); + if (ret != 15) + return false; + + unsigned int count = 0U; + RESP_TYPE_MMDVM resp; + do { + CThread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError("The MMDVM is not responding to the SET_FM_PARAMS3 command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); + + // CUtils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { + LogError("Received a NAK to the SET_FM_PARAMS3 command from the modem"); + return false; + } + + return true; +} + +void CModem::printDebug() +{ + if (m_buffer[2U] == MMDVM_DEBUG1) { + LogMessage("Debug: %.*s", m_length - 3U, m_buffer + 3U); + } else if (m_buffer[2U] == MMDVM_DEBUG2) { + short val1 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage("Debug: %.*s %d", m_length - 5U, m_buffer + 3U, val1); + } else if (m_buffer[2U] == MMDVM_DEBUG3) { + short val1 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val2 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage("Debug: %.*s %d %d", m_length - 7U, m_buffer + 3U, val1, val2); + } else if (m_buffer[2U] == MMDVM_DEBUG4) { + short val1 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; + short val2 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val3 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage("Debug: %.*s %d %d %d", m_length - 9U, m_buffer + 3U, val1, val2, val3); + } else if (m_buffer[2U] == MMDVM_DEBUG5) { + short val1 = (m_buffer[m_length - 8U] << 8) | m_buffer[m_length - 7U]; + short val2 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; + short val3 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val4 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage("Debug: %.*s %d %d %d %d", m_length - 11U, m_buffer + 3U, val1, val2, val3, val4); + } +} + +CModem* CModem::createModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug){ + if (port == "NullModem") + return new CNullModem(port, duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); + else + return new CModem(port, duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); } diff --git a/Modem.h b/Modem.h index faf0917..e28aad4 100644 --- a/Modem.h +++ b/Modem.h @@ -19,103 +19,217 @@ #ifndef MODEM_H #define MODEM_H +#include "SerialController.h" +#include "RingBuffer.h" #include "Defines.h" +#include "Timer.h" #include -class IModem { +enum RESP_TYPE_MMDVM { + RTM_OK, + RTM_TIMEOUT, + RTM_ERROR +}; + +class CModem { public: - virtual ~IModem() = 0; + CModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); + virtual ~CModem(); - virtual void setSerialParams(const std::string& protocol, unsigned int address, unsigned int speed) = 0; - virtual void setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int txFrequency, int txOffset, int txDCOffset, int rxDCOffset, float rfLevel, unsigned int pocsagFrequency) = 0; - virtual void setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool m17ENabled, bool pocsagEnabled, bool fmEnabled, bool ax25Enabled) = 0; - virtual void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagLevel, float fmTXLevel, float ax25TXLevel) = 0; - virtual void setDMRParams(unsigned int colorCode) = 0; - virtual void setYSFParams(bool loDev, unsigned int txHang) = 0; - virtual void setP25Params(unsigned int txHang) = 0; - virtual void setNXDNParams(unsigned int txHang) = 0; - virtual void setM17Params(unsigned int txHang) = 0; - virtual void setAX25Params(int rxTwist, unsigned int txDelay, unsigned int slotTime, unsigned int pPersist) = 0; - virtual void setTransparentDataParams(unsigned int sendFrameType) = 0; + 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 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 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) = 0; - virtual void setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel) = 0; - virtual void setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, bool noiseSquelch, unsigned int squelchHighThreshold, unsigned int squelchLowThreshold, unsigned int rfAudioBoost, float maxDevLevel) = 0; - virtual void setFMExtParams(const std::string& ack, unsigned int audioBoost) = 0; + 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); + virtual void setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel); + virtual void setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, unsigned int rfAudioBoost, float maxDevLevel); - virtual bool open() = 0; + virtual bool open(); - virtual unsigned int readDStarData(unsigned char* data) = 0; - virtual unsigned int readDMRData1(unsigned char* data) = 0; - virtual unsigned int readDMRData2(unsigned char* data) = 0; - virtual unsigned int readYSFData(unsigned char* data) = 0; - virtual unsigned int readP25Data(unsigned char* data) = 0; - virtual unsigned int readNXDNData(unsigned char* data) = 0; - virtual unsigned int readM17Data(unsigned char* data) = 0; - virtual unsigned int readFMData(unsigned char* data) = 0; - virtual unsigned int readAX25Data(unsigned char* data) = 0; + virtual unsigned int readDStarData(unsigned char* data); + virtual unsigned int readDMRData1(unsigned char* data); + virtual unsigned int readDMRData2(unsigned char* data); + virtual unsigned int readYSFData(unsigned char* data); + virtual unsigned int readP25Data(unsigned char* data); + virtual unsigned int readNXDNData(unsigned char* data); + virtual unsigned int readTransparentData(unsigned char* data); - virtual bool hasDStarSpace() const = 0; - virtual bool hasDMRSpace1() const = 0; - virtual bool hasDMRSpace2() const = 0; - virtual bool hasYSFSpace() const = 0; - virtual bool hasP25Space() const = 0; - virtual bool hasNXDNSpace() const = 0; - virtual bool hasM17Space() const = 0; - virtual bool hasPOCSAGSpace() const = 0; - virtual unsigned int getFMSpace() const = 0; - virtual bool hasAX25Space() const = 0; + virtual unsigned int readSerial(unsigned char* data, unsigned int length); - virtual bool hasTX() const = 0; - virtual bool hasCD() const = 0; + virtual bool hasDStarSpace() const; + virtual bool hasDMRSpace1() const; + virtual bool hasDMRSpace2() const; + virtual bool hasYSFSpace() const; + virtual bool hasP25Space() const; + virtual bool hasNXDNSpace() const; + virtual bool hasPOCSAGSpace() const; - virtual bool hasLockout() const = 0; - virtual bool hasError() const = 0; + virtual bool hasTX() const; + virtual bool hasCD() const; - virtual bool writeConfig() = 0; - virtual bool writeDStarData(const unsigned char* data, unsigned int length) = 0; - virtual bool writeDMRData1(const unsigned char* data, unsigned int length) = 0; - virtual bool writeDMRData2(const unsigned char* data, unsigned int length) = 0; - virtual bool writeYSFData(const unsigned char* data, unsigned int length) = 0; - virtual bool writeP25Data(const unsigned char* data, unsigned int length) = 0; - virtual bool writeNXDNData(const unsigned char* data, unsigned int length) = 0; - virtual bool writeM17Data(const unsigned char* data, unsigned int length) = 0; - virtual bool writePOCSAGData(const unsigned char* data, unsigned int length) = 0; - virtual bool writeFMData(const unsigned char* data, unsigned int length) = 0; - virtual bool writeAX25Data(const unsigned char* data, unsigned int length) = 0; + virtual bool hasLockout() const; + virtual bool hasError() const; - virtual bool writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) = 0; - virtual bool writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type) = 0; - virtual bool writeYSFInfo(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin) = 0; - virtual bool writeP25Info(const char* source, bool group, unsigned int dest, const char* type) = 0; - virtual bool writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type) = 0; - virtual bool writeM17Info(const char* source, const char* dest, const char* type) = 0; - virtual bool writePOCSAGInfo(unsigned int ric, const std::string& message) = 0; - virtual bool writeIPInfo(const std::string& address) = 0; + virtual bool writeConfig(); + virtual bool writeDStarData(const unsigned char* data, unsigned int length); + virtual bool writeDMRData1(const unsigned char* data, unsigned int length); + virtual bool writeDMRData2(const unsigned char* data, unsigned int length); + 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 writePOCSAGData(const unsigned char* data, unsigned int length); - virtual bool writeDMRStart(bool tx) = 0; - virtual bool writeDMRShortLC(const unsigned char* lc) = 0; - virtual bool writeDMRAbort(unsigned int slotNo) = 0; + virtual bool writeTransparentData(const unsigned char* data, unsigned int length); - virtual bool writeTransparentData(const unsigned char* data, unsigned int length) = 0; - virtual unsigned int readTransparentData(unsigned char* data) = 0; + virtual bool writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector); + virtual bool writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type); + 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 writePOCSAGInfo(unsigned int ric, const std::string& message); + virtual bool writeIPInfo(const std::string& address); - virtual bool writeSerial(const unsigned char* data, unsigned int length) = 0; - virtual unsigned int readSerial(unsigned char* data, unsigned int length) = 0; + virtual bool writeDMRStart(bool tx); + virtual bool writeDMRShortLC(const unsigned char* lc); + virtual bool writeDMRAbort(unsigned int slotNo); - virtual unsigned char getMode() const = 0; - virtual bool setMode(unsigned char mode) = 0; + virtual bool writeSerial(const unsigned char* data, unsigned int length); - virtual bool sendCWId(const std::string& callsign) = 0; + virtual unsigned char getMode() const; + virtual bool setMode(unsigned char mode); - virtual HW_TYPE getHWType() const = 0; + virtual bool sendCWId(const std::string& callsign); - virtual void clock(unsigned int ms) = 0; + virtual HW_TYPE getHWType() const; - virtual void close() = 0; + virtual void clock(unsigned int ms); + + virtual void close(); + + static CModem* createModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); private: + std::string m_port; + unsigned int m_dmrColorCode; + bool m_ysfLoDev; + unsigned int m_ysfTXHang; + unsigned int m_p25TXHang; + unsigned int m_nxdnTXHang; + bool m_duplex; + bool m_rxInvert; + bool m_txInvert; + bool m_pttInvert; + unsigned int m_txDelay; + unsigned int m_dmrDelay; + float m_rxLevel; + float m_cwIdTXLevel; + float m_dstarTXLevel; + float m_dmrTXLevel; + float m_ysfTXLevel; + float m_p25TXLevel; + float m_nxdnTXLevel; + float m_pocsagTXLevel; + float m_fmTXLevel; + float m_rfLevel; + bool m_useCOSAsLockout; + bool m_trace; + bool m_debug; + unsigned int m_rxFrequency; + unsigned int m_txFrequency; + unsigned int m_pocsagFrequency; + bool m_dstarEnabled; + bool m_dmrEnabled; + bool m_ysfEnabled; + bool m_p25Enabled; + bool m_nxdnEnabled; + bool m_pocsagEnabled; + bool m_fmEnabled; + int m_rxDCOffset; + int m_txDCOffset; + CSerialController* m_serial; + unsigned char* m_buffer; + unsigned int m_length; + unsigned int m_offset; + CRingBuffer m_rxDStarData; + CRingBuffer m_txDStarData; + CRingBuffer m_rxDMRData1; + CRingBuffer m_rxDMRData2; + CRingBuffer m_txDMRData1; + CRingBuffer m_txDMRData2; + CRingBuffer m_rxYSFData; + CRingBuffer m_txYSFData; + CRingBuffer m_rxP25Data; + CRingBuffer m_txP25Data; + CRingBuffer m_rxNXDNData; + CRingBuffer m_txNXDNData; + CRingBuffer m_txPOCSAGData; + CRingBuffer m_rxTransparentData; + CRingBuffer m_txTransparentData; + unsigned int m_sendTransparentDataFrameType; + CTimer m_statusTimer; + CTimer m_inactivityTimer; + CTimer m_playoutTimer; + unsigned int m_dstarSpace; + unsigned int m_dmrSpace1; + unsigned int m_dmrSpace2; + unsigned int m_ysfSpace; + unsigned int m_p25Space; + unsigned int m_nxdnSpace; + unsigned int m_pocsagSpace; + bool m_tx; + bool m_cd; + bool m_lockout; + bool m_error; + unsigned char m_mode; + HW_TYPE m_hwType; + + std::string m_fmCallsign; + unsigned int m_fmCallsignSpeed; + unsigned int m_fmCallsignFrequency; + unsigned int m_fmCallsignTime; + unsigned int m_fmCallsignHoldoff; + float m_fmCallsignHighLevel; + float m_fmCallsignLowLevel; + bool m_fmCallsignAtStart; + bool m_fmCallsignAtEnd; + bool m_fmCallsignAtLatch; + std::string m_fmRfAck; + unsigned int m_fmAckSpeed; + unsigned int m_fmAckFrequency; + unsigned int m_fmAckMinTime; + unsigned int m_fmAckDelay; + float m_fmAckLevel; + unsigned int m_fmTimeout; + float m_fmTimeoutLevel; + float m_fmCtcssFrequency; + unsigned int m_fmCtcssHighThreshold; + unsigned int m_fmCtcssLowThreshold; + float m_fmCtcssLevel; + unsigned int m_fmKerchunkTime; + unsigned int m_fmHangTime; + unsigned int m_fmAccessMode; + bool m_fmCOSInvert; + unsigned int m_fmRFAudioBoost; + float m_fmMaxDevLevel; + + bool readVersion(); + bool readStatus(); + bool setConfig(); + bool setFrequency(); + bool setFMCallsignParams(); + bool setFMAckParams(); + bool setFMMiscParams(); + + void printDebug(); + + RESP_TYPE_MMDVM getResponse(); }; #endif diff --git a/ModemSerialPort.cpp b/ModemSerialPort.cpp index c34cb3d..51ba01d 100644 --- a/ModemSerialPort.cpp +++ b/ModemSerialPort.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016 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 @@ -21,22 +21,22 @@ #include #include -IModemSerialPort::IModemSerialPort(IModem* modem) : +CModemSerialPort::CModemSerialPort(CModem* modem) : m_modem(modem) { assert(modem != NULL); } -IModemSerialPort::~IModemSerialPort() +CModemSerialPort::~CModemSerialPort() { } -bool IModemSerialPort::open() +bool CModemSerialPort::open() { return true; } -int IModemSerialPort::write(const unsigned char* data, unsigned int length) +int CModemSerialPort::write(const unsigned char* data, unsigned int length) { assert(data != NULL); assert(length > 0U); @@ -46,7 +46,7 @@ int IModemSerialPort::write(const unsigned char* data, unsigned int length) return ret ? int(length) : -1; } -int IModemSerialPort::read(unsigned char* data, unsigned int length) +int CModemSerialPort::read(unsigned char* data, unsigned int length) { assert(data != NULL); assert(length > 0U); @@ -54,6 +54,6 @@ int IModemSerialPort::read(unsigned char* data, unsigned int length) return m_modem->readSerial(data, length); } -void IModemSerialPort::close() +void CModemSerialPort::close() { } diff --git a/ModemSerialPort.h b/ModemSerialPort.h index 6de56a7..d6761ce 100644 --- a/ModemSerialPort.h +++ b/ModemSerialPort.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2016,2020 by Jonathan Naylor G4KLX +* Copyright (C) 2016 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 @@ -22,10 +22,10 @@ #include "SerialPort.h" #include "Modem.h" -class IModemSerialPort : public ISerialPort { +class CModemSerialPort : public ISerialPort { public: - IModemSerialPort(IModem* modem); - virtual ~IModemSerialPort(); + CModemSerialPort(CModem* modem); + virtual ~CModemSerialPort(); virtual bool open(); @@ -36,7 +36,7 @@ public: virtual void close(); private: - IModem* m_modem; + CModem* m_modem; }; #endif diff --git a/NXDNControl.cpp b/NXDNControl.cpp index 119fad6..1e200c4 100644 --- a/NXDNControl.cpp +++ b/NXDNControl.cpp @@ -737,7 +737,7 @@ void CNXDNControl::writeEndNet() void CNXDNControl::writeNetwork() { - unsigned char netData[100U]; + unsigned char netData[40U]; bool exists = m_network->read(netData); if (!exists) return; diff --git a/Nextion.cpp b/Nextion.cpp index 2029aeb..fa6851e 100644 --- a/Nextion.cpp +++ b/Nextion.cpp @@ -36,8 +36,6 @@ const unsigned int P25_RSSI_COUNT = 7U; // 7 * 180ms = 1260ms const unsigned int P25_BER_COUNT = 7U; // 7 * 180ms = 1260ms const unsigned int NXDN_RSSI_COUNT = 28U; // 28 * 40ms = 1120ms const unsigned int NXDN_BER_COUNT = 28U; // 28 * 40ms = 1120ms -const unsigned int M17_RSSI_COUNT = 28U; // 28 * 40ms = 1120ms -const unsigned int M17_BER_COUNT = 28U; // 28 * 40ms = 1120ms #define LAYOUT_COMPAT_MASK (7 << 0) // compatibility for old setting #define LAYOUT_TA_ENABLE (1 << 4) // enable Talker Alias (TA) display @@ -824,79 +822,6 @@ void CNextion::clearNXDNInt() sendCommand("t3.txt=\"\""); } -void CNextion::writeM17Int(const char* source, const char* dest, const char* type) -{ - assert(source != NULL); - assert(dest != NULL); - assert(type != NULL); - - if (m_mode != MODE_M17) { - sendCommand("page M17"); - sendCommandAction(6U); - } - - char text[30U]; - if (m_brightness > 0) { - ::sprintf(text, "dim=%u", m_brightness); - sendCommand(text); - } - - ::sprintf(text, "t0.txt=\"%s %.10s\"", type, source); - sendCommand(text); - sendCommandAction(122U); - - ::sprintf(text, "t1.txt=\"%s\"", dest); - sendCommand(text); - sendCommandAction(123U); - - m_clockDisplayTimer.stop(); - - m_mode = MODE_M17; - m_rssiAccum1 = 0U; - m_berAccum1 = 0.0F; - m_rssiCount1 = 0U; - m_berCount1 = 0U; -} - -void CNextion::writeM17RSSIInt(unsigned char rssi) -{ - m_rssiAccum1 += rssi; - m_rssiCount1++; - - if (m_rssiCount1 == M17_RSSI_COUNT) { - char text[25U]; - ::sprintf(text, "t2.txt=\"-%udBm\"", m_rssiAccum1 / M17_RSSI_COUNT); - sendCommand(text); - sendCommandAction(124U); - m_rssiAccum1 = 0U; - m_rssiCount1 = 0U; - } -} - -void CNextion::writeM17BERInt(float ber) -{ - m_berAccum1 += ber; - m_berCount1++; - - if (m_berCount1 == M17_BER_COUNT) { - char text[25U]; - ::sprintf(text, "t3.txt=\"%.1f%%\"", m_berAccum1 / float(M17_BER_COUNT)); - sendCommand(text); - sendCommandAction(125U); - m_berAccum1 = 0.0F; - m_berCount1 = 0U; - } -} - -void CNextion::clearM17Int() -{ - sendCommand("t0.txt=\"Listening\""); - sendCommandAction(121U); - sendCommand("t1.txt=\"\""); - sendCommand("t2.txt=\"\""); - sendCommand("t3.txt=\"\""); -} - void CNextion::writePOCSAGInt(uint32_t ric, const std::string& message) { if (m_mode != MODE_POCSAG) { diff --git a/Nextion.h b/Nextion.h index 713fc90..488372f 100644 --- a/Nextion.h +++ b/Nextion.h @@ -70,11 +70,6 @@ protected: virtual void writeNXDNBERInt(float ber); virtual void clearNXDNInt(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void writeM17RSSIInt(unsigned char rssi); - virtual void writeM17BERInt(float ber); - virtual void clearM17Int(); - virtual void writePOCSAGInt(uint32_t ric, const std::string& message); virtual void clearPOCSAGInt(); diff --git a/Nextion_G4KLX/NX3224K024.HMI b/Nextion_G4KLX/NX3224K024.HMI index ef8698beddb7d7d51998c585cf5e558e600f6af7..61c8067a911ca9b0d891fb3cfcd6e9329f05d3c8 100644 GIT binary patch delta 437 zcmdl#OJ&u3#R(G5YI+2Ep3cTTLudQ7YwY^(r@!yw;M*>I zj6;xd`u_VIn$!FAIU2VYEay1Qxa~HOV3eQEdxOJ#`kd1oy4wxzax7+^zV;M{_4EnX zIcgXKA#%|dICQ52<&I3}H{h5EHgWpviyT7JgP(J(+%EEhL!W~i7994B$rBSrrzd~l z5Mc(2P51i9Av=9KkOx-5%8y}`3Q4?0n8{6T>$6UGr9wn gupDJR%RD{*6UU0}`ky(jh)rka<+Nb&?_O610HNl3x&QzG delta 11828 zcmc&)2|Scr*gvzG$zCKyMyQZIMG>t;DO{AIxGj>5(O$}+(C#wyb|oQXD_fZaCC_jZtkbnZW6kqO^%j2eMpUJ+cE7h_{4nI49nz^@&s9Z?% z!@?g9{_yZefIk6-ivaP08e`K8+%tS;6lMn=@sPrM)1xpp__7Tp3lNNkfDe2!7s+8~ z-Q*f#;pRvWo^TUV#l`H)3#Iu6`iX}i&UrXj*s)$RlAUDD@8I57s34v__zSRi7H;1) z`Cw-W>?v!+YAYcMlBBVY9mo=8foX{1N5WD_*ozGb4R)5r7D^+SKF%m{w9~>-G(KkU}rgw_A3Pv1pDiBIbuUXk^Egy(Rl4)3&aHv zm3MJ;?eH37g-|4$&9lM+iE1Luz&VpV`9_E=YT3szqB7!?HS{HUv=xFQR-isr!0t5X z_?cscAm54#FC_RT)Mcy%S;H1MkFj82#Da;8Z!HMtSpegZPbDZ|g|{H-bMWTSDGo3e zJv=Mi-4IMUjKwH3L`Td>vgWu9ho6y!Ok9iI2M6m2*4A;u;dzE@h%46+*hiXRU(WS) z2NIP`m_Y#G`kv9D;AepF7LFmfLg{XLaLq|LR8CNqmjN&wpSE(1p$Bg^+o4~~zWeW8 zT@e*~&cO?M2j(Q-OD1^37D(Z~v(i{+65{zBZzMc46v;kRZVxZrIMk^-f~g98-z&aCj9r-Ov;t9vLH!Xv@HnIc#ZOl5^8z zeK1uA%~t3hbk>pl9yRN`4QRBDMkhH;5st#nBrD{JHqLlB$GI7zgpJpxQx7br%bUqg zNV2wYbB7n2+r}_m_(^|m4<}vl@&Fnk9)p>OkYix!i0FzyoJL1txpzszyCwv$GJ?6cN8}XywY&>t-TQ{00z^k&hqltpNys{om6y>dPYeo|% z@m2-1(L_n!a<&o@VdY$$ig7|LQ!#R+kWltr!xqC9v<$4I>Bjx1*96}5SvS$w)iN^* zvj&g1|9Y+;NiP?e3G+UY6wnKLVtOhE=)ss$QHwIr!@aik>VJi#0tx*p=Z}0MdN1pf zh4j19k!dSNsksP=eDfmqi?&o&rLo11|h=@g+(n&jX!2VsieMabn-N%io)@k zxION}rUa_quBwa>E*z`w_v3lSCTMW?3?UKU+wtuno-^V#hu8MlZGWRacR1Hkaaxfh zRVg!Oj4QvL26dn~>cDs{$CmxP9Zgcf5?)BYXXjFydTddr#%m216XCzchXdV50MgIM}Ur)xs(Aa-4^Q~xy%hj6x z{nGp&_n-29Z-M6_XmT%K-T|J zG=^hN=;6JUYyN9Zh@6sR(N9mmI2FDtbN_37gm@(oeU)LPRziI&vRj_~ldEJL?(yiT z>~Z@4fD;bxKJPE^?(T0X@SN(^V(5T7z+kk0!yyT!H zn)MF_7Y#W>_r~|yqYFZ@Sbv>z4L>D?`wbzjaFW#>J_Wry_i${^k*=+4dONxUZR2|x zs919e`>&M)V=q}qca#+#-eZg%YQ|mkO(efx8>&%>8|2xK{%(=E-cR>UUn<4E`)*i$ z{sf%y>u3FF%fe^j-eY>Y&}bS-{irT;rKqy+hD4CQ+xm=^GwjZV<4dyBjAU~^KE;1w zH}S3yjfCF#;?`@8d{&+N=|+PWLf!G@Ilw&#|5WAYdyo~qY&1q%;vd9#{=)Vo zsjnZ+Mz!(f{jQb6=AYhlB;#AZ4Zye09sN+A>iF`uYaQ+2e_Z{1H^bX=%^F|cPA%Db z-Z@u<*-zJaUg1|5Umit+zxmXP4fyL@>%M&+F5SfOB~szmlz$v`J+tt;H+if~Ug?z` zU*2A=k?+M{&KT+O`bm2u00w+%dn z;++<%{d6ykW)F7*#>a65`&shOuR?w?jrbz}M%#eZ!{;c!kW23UM|m>G)$)Y-$XCv< zr4lM}TybYi96R{mKV?7t{y}%ujVSu!^Vr-IwdwcZUDO z5pmvdq(J{XMfgLxcaDH?eSRwZ$l9?3Mn7=|N9z8QkdK-*N@wI)?dPFI2Xf|%)%YhN zd@{7MVrHvCCgvQtYYK8 zsl*%x=CqNeX{^gQ-FKu$&W=a2o70sysh@wK8q0M7hkQ?z|7VemJ0-mwQn3K{a2QE% z#qWSy^c`4ua>5afs8Kv9+3jm@8ATv4q81B@-iui* zJLN)QzNfNQ1h#OKll1K0t(>F?e*2u(69TN9qzR(4EVX?7Cdlrp@us;~J6N<|3}5%s z_POKD&xJu1HLXc);U3#>Zys_ynwm&UQni`gPz2pXs)QflTE+QOw;2oB8_@(aJpy$SP z_N9{mThx;`(p=JVV~q!mk3g1rEHmpwu!L2 z>nkTa1(N0FyU;9%>_)Qax}&*Y%iis!R;JRi7w;U|9rfi_!J|2DOfqfYO}=27DcR6m zKrbh8U#`}g?ezS?+Q2zXmt3#)jr)~1GCfJZW6w|e(4Tudx9WfnGuLRdugE&)%LkJv zHY)q;UpnR!uKLNyUmPsB-{9?$TF@S?)@U?1u$ORtzaVppv8L^|;tvKBShU0G0`naj z_s=n4eln?{=BqRqeSLkQoo29*Wvm{)-Q{XQd*b^Bqk~7Hd@`oXP%?G4z#g((9aiQ# zH9KrFi)mSqPbdkfO?mj@oFJRMjuBP$B|s(@)H|N52wt8U8uW$DZe7%jIXyuyuet9T zjkQ!alz-r=^_-`Ytw{53k}SNRW&82 zX%4#Udi({|+MEZT_D&R4YhO=&H*liNs$o-B@5G%n`kp|qsvH;UnXNNF>v&H$Zcdhr z-;#GWiZol6pMe>q1;MBdEDQaI*5ugg2|o>T$rgw z3$3@5guQt;=V<-2lWKMK$?tAe% zin_6PBC==TO=P8}c5zBw@*(E3m1~|g=@&IaeF%D;irKX6chM>Cq5^t!vgcnil!Y|c zNMpA*Wb(Yq*~I)ap46r$!$XJmOgh=Qd6!jClLTm%-@QPRRdP8ptvy>7|3L)GzuQRh z@0ze{%7dQyZ;D-H$oRXDCdHMP#%wCNiVMe!Dj>`9&U$Ix%3>@avB=Y3`Z-_BE|_j} z?x0VrTi-m|Gp7=!Q{lajR2NgY7Rv-Q8}^lA+9y&msnTtukEf^{MRWyz*yT;_6Bi7Z>C!dTwSO zRh^sVm2a^n-eln+OD5qHXy3EYs#w3m>dk89wxhBkF#Zl7aO}*M+Ac+n0|7EVcjn-Z zcSTW!USX*32TrWg_48aALiW5hhjnmKb!3o1p*nsmop@}cwAlKxpydzUJtm!XI%tND z^_O{fiQ3-FlwcV@)pxBmk`tyl8|FO>Qd5M^jKZC&cTWMdp8KUeX{% zE5=oIDl!l1`s`3D6pWi8D!uuDZ|!X3R~dS&`cf!Y49n1x`$N_E)ugjq^?P9+eeti= z<8;eGf6)uo&k!Hh!$?L3o}97b%G5O>*$z%-RfR;#<$)8g_idsM*$b3@HmKDs^16!c zCw&6jg(GI)2$HVu)tj&`XWMexm8m8^f8hN0yfyGlQ&6i8aCb-#mA!qU@k?ot!&9R@ zLofF}_4puCw(7;EvLN9*6#cV1q^BDO+p46kx!>wiE8%C&Eb0y3%vvlXNpbeZKaQ+S zoyOLyyx&ml?zDO+>yz7+{G7gZ;ixB*xB! z67(Kns&m~hk*yf#Wm!(*F(yOYc{`4U#F%HTbZ+gfwy8r$5SZ>YqY%E`TAMA!ItJC%p`+i;8$`E zCgxn5{KwLKkw-bSTxTG+PbF$rAPaZ zKd_v_Jld%!t(eseL_`=;=UP*1yc-i8WzOeKU%1h4#qJBU6IA_GR9H{v39uL0EmoDI^fkY($-36>R}zZ) zMb6H#Q_&%;AQVs8M+jXbek=Dc+WckqwKlpeKcmpI1Eoc!u&_NTEi}9H^+c4pvpJUW ztnVY?e2Nd`==RUM>9*13VWZ?KvA{`ZjUtY8><(UIr_8KgYX2;Lc~ODI?2ir?vmOnU zPA(1&-XT^%^3Y%x*PgdiS?%xFpHL`qY^nq+4UkWmW$~aca}7E#2>goIR;tDONr6jYCXf9d2J? z-22+p&Q`GEewz>**CkWhHDN8Ay?n1X_@=6V{npR^{VV!K zo;6R{up)o(8qIs_Z%PWpC$ze~h~z>W6=t?WY#UBb>hqFZDxza&MeV5HTFRK3?3lQq zOdcjkyTt+jhe6C|lPSkDbYD11>?+E8Im7tXspYTbYpzg2u7}jzgsC%!#oUn8R~(qM zWoj~sY4F$g&ax*;uW4J;cfGkG#`=BU%UbuN*`LsZw%yYrE14}kb z9+V`ul+8b$^C^!6z!T-jK1L=sO?m;Czt_fNjF-DFt$8s7cUsvR=Q0Uwi3A1cy( z`r^EtXj~N4&0n;Z*+8qi(M=6o9bKQSts=;ZZuZOeP)<&AfP2`!r-jbuG}~rnH)h=F zKAV>o+k_4zSlZd%dLZ?|GCCz`ci2o8Q#B#?<|?x^TmN zp_5tLy_EusDv|?i?Wk8?6VvwC58TenV7d$cvIzW07;Gi7LqBYeYIm5SbxBnvD@gqDzT0Fd~R& z6mca+6p8+mxC|qP1m7d-5y=1p#D>HXt8Ah+QZ$>Sj?rY~za^f-GUiMsm0>Ve$fhu& z9&%ibq>jA5PrQdzL=b~9XAq(WiH!JMB9f8or-%(a1rVr!FNZ^n1TYxP&=B`O17e5_ zk4waU4B|bDB+7U^lXMq@P_ZNdgg%?3gLq6LsWUoflQ!UgK)ixO);uK|Adlyejvya| z_+uV_A#%q2O*aBX#v!vbNQvUwdR#rYD4@tEy*l4>+=NybU?9h2@-+Nh{NB0WvowGl$kG5=@Va;lJkqI0N{|wQ zd_?&3StJ0zwp~H>6#(SUGcM?ps-$QLe87Qlh>GAY#6w_yECANv1VmkM2jW^F6bE49 z2}8sKaZY<8L?V#pw5M|D(>ZN9h*%)cY0rR&14VfI8R=Q3nJ=Gz5HnM^4Q;Q4s0^qO z6+p-}K!e)V!iVL6d>z%!5uy_Cg=hfoK-2{vAua@}si?T^5HY|7A|AML+PffP0fp1{ zfQSRTAu56M5Dx*dG*r1w5UoKPM0M~Iq7D#CN2RTTNYe#0c(VbO5Oo1D1JzCwq5`mj zXa>R|E(diGjeu+>Dta+Q1KhCr-+^c0zQZezL1xSqC$;59HmedYwvgSPtWO}{ihuAn>+WLotbQA z_nY0%FLs%~rPFZW>i(52vtj;~TW4{WS{Crad(GGwXZ8oo7UjDwSPQC##1V;5(rEd!%LWH6D?`;G9<)u_pMyi` zta}5$GBol3lX;cBrsvk=S~a(=CEZOaWkTDE=p7opKZ|L2pQS9#mw^6Ykz z`mS?kIoRI3VIdGq7?~a2o-WT`-FbWA`0ToCYYU&x=1;Tjh0li@=JffjGmwRBEJ8jG zb^{{taGBK!yLtc#_A9J>TyZ1N79sp#<-zA)y#w>jb{^^m0$t!@uH6C{7W6LjuCYpj zKb3i3+x-zVro6pD=Nr62t2*d=1G}&lywcm-6Pz#iimbUoev(~<8$43xH3SD!-X+%9 z;F}b?C*7det=?O~^4?y7b!o7(H@mf2L1dt}KNwr#`Bv9pRt3AQp`f9kw<+j&lh@ta z5DdAA-8-H+?w%mj$1Adb_JYg$cwgDIIZX%ts4ov34Swp&4cA42*%jRInSShg1_%0i zUpIfVIq=Ibb(lvN28Xt}dBI2hy}j0mVCl^qKJzOxybHVO-lpMa{=ngF!MFk3n z^L*p&&G{CiBkj_3z8y#Ke3u5_jNou_p&1^CLG+HM;qxOoTp#Ql#ocZx{*T>`xr@8~ zIGA-8A2+S>o#botUl&2|IQ5%n%<6Eev4rbDF?4>?V zssB>_X&S>t>Y9!D-@MDY$Wjhb8_kr%)Io$M(U1CgfLa(&9ZaBc)TpVYE@oLgzJ=Lb zu`x#jbE$**G>P}AkMF64bJW2F8b`!7O>^p^rE)83Bc>du?Qj*HgmpB8dK$xS<=1Hh z?<*g%kwH1yIA*w%`shbPxRoZckjAlI{hR9FRiDeJA&z3|<3>Ft71Y7+Xbi*D3|BKs zc{Ft~M)_W90kzRi`BLg+P{>6RcTpcRG%(W)pq9q5OwH5O#Y*K>)W#a+wba40 zG>J3RN0{@B1&=ytPU9$4(}B9^q}-X>C{fOI<-$QXn#Kg`W1a@)t6xO(@SK`@>f%M^ zP1ME~<*n4gOEiTasgFxIiCV~~4%*QKx~sXGy0}jHdTOIrMk{5s1ozT4$l>H2LQ5J$ zv2s@$K@a6JnnV?iV*&NCS^W<6KdNsLHvQ!^gsW*157Rhiso$vnMViLBFt7hSkHZCgvOnJfQnBO2teTSLw)RqEH$JiJ0(WOiv_w^sHk@2582P&U)8g9e&L3(l>)tf+-6sDrC$ z9{Nxh{pBrk5VbH^d5H2*YGb(a2m(V6O&Vr*RyoE{C8KlZdo5C*7X4D~BnkX`0NS9~YdbH85TS6Da3tHMNxUwDN4qXO;b+O>d&daKsik( z%wuz@g_g>3iDA^oG!0a#ucmPzQ!KZW$X0$ec^~Y%pPb;sc5&T7YD^22jYhM3xq`0lPm`EP+)wi`L;Y&?>uCz#&;fkY&#v9bf zY13zLMgxsB4^CUNQSHXmFpfGHpWz~g>8fU^s!^_`eBCS0rhMHi&!v3bQ@-vgU-#$0Sil{Ziu&neea2QSbFUQzR^nq4%857c}}T^vz9N^KlhK0%9Mb>Q{SkJ4O(5YQMl zD!)jR_>RVrU1UzK6Ahu1CNZAIF+=@o_3LRGjg*&gvDv-@<$D87VH{1MTK!7(>u3a< zDX;%6T=1I^8p8oop+U{N$_J^750pQoHV!L)OdTAfNfdN6hwMcylu-xeG>(C422mGx zDi5JHhAErR$PO~2xJY6Z^|40-uW4XEjpLY_^2T z*U6kv4t3E&IhXQYQaMUnqicqXI_#t&?4~h%sC1i)F(l4hOqkn_f%DPr!jutn>W7szSsTnt8@SN z-c$FUx_xd{h2po`9y#0o4x+9%WGsy#`RT13r{kca5Dk0C2}N?!4Meg$m1s}>l#OSaQAh!liY^*bEe~-K2%*3;@xunP5qW7drQSftMyDRYq%WJ{Dj#j z_gC*NmEWx46UFXN^;X(M_roPW92o@%SH}0Km0k9hR?ZwHc30@>_K@z4Tf5IQU)Btm zDzbx)jL4xSCagc1yT$Z6eQb@GyGBpfc0Dm(C<8b)Oa_M#E!9N zkY;b6Ms4jwYE#28!&IfqfSM&)ifS^e96Pn7N&I~`w_xgN~`U9I-06*I1PT*{e6 zCn|_7Iew%!!uHtCLHKr=FAn!|6c!aWSLGsYqFxy>xh!*X*Ax}ziHSaEhMZiyJ-NN5 zqeMYFN3f{Sr6D*a16uZsdu5-w%*?etvZydao#!q&eCL%|5-HZgmP=w_QQ_rQRC?lk z;Wf>{C>um&~{=&ADh` z|N9=mSFYw>uGuH;g+6_(_~=vJFDA5c=;Fp%N>6d;ETxrtqExs$sC_S7GX*s%l`{_> z`t80$5xRcy2d!m4W@@d-*^J+PL{4;Huo}(8h%6LZh}N}|S*X&^jh;9=4aw|e|K=kxZwH!kPYpL=>9DFC?Le^zV+&K@H{*_jo7~ z3I9*{b@w-Sg!u3h>o0bFiYwS)2H{*3ol4hP8-ksNti5VPzn@Cm<^6BIaX&IaO-f1OvAeVr_#Fo>c8sxpkAKkQ4|{hT#~-at;x=1&#LBI= z*uoR`a&ogGlYzaP^$#gUg}TiDa*ao5hRpdTV~$;;?=3}^obiToyO{Ha(k8Ze=E>?F zwLNN!Q~CzG%>R#mYRa0<0Tpxo|SVLVHXKv}m zrS0~F8<@VWp_jWtdu`c0;d5JOF{d%V$?R~BsG1jbu0Xw8PNzI!Xzib@8;C`l0-_+* z>5*qLJ?Tg5Z;?|hcZG6aS?%uVF>O8dik$DrYtw!nbpBuedOoSE+O>GsV%NIJxJ1V7 z9<}aF!;|wc|5Z5ZC)QWWAl#)tzBB9SztMApYc&Kvpiv+Eo4p$b*2^MZu7iFP|K=2| zRdVZZb_zqTdux>q{O!)dOWILyt<)QS;}dc0)c$A0#(VLy`}xv z)$abEe782zx-nh)ZnQ1|m7~6Oosr)G^7nOh=q9d{ypy{~^UPl-nsrdG3$ESwAN|IK zP9NGo_Gr52oJS_T5950NT!7VCerk|RZ(Ew2In?emdslFs5jmRC0vC5d;(&K-RUQlK zdCu!o4jwxC_s9xq>!(&p*ViTuI=}a9!*3e&p{i>;CkjV>KXVhg+EaHX+R1+DVz7R4 zM?{`-!O+~Iaz?rPyQWNtVo?=EpzPIS~8M!dB8t*@>N%%RtZn6GNIQnLas5^uH9UAX{Z6E5W7w{Bxtrrx5ob z_QU_us+zxtUC0So+izOq0~iTSIHtf%5% zxPTqpXMeun(`Ycp<7)1=UaVv_xXI~=V8~6dp|3_AO?25$_{A4q)=YU>!;TttGT9Y+ zz`XF<`f;Ob`DoO^vk~`~4X`X6PZ^{bs}+d-VOI1HvdDplmwKoj_GruV z>|Y7KdFySuoP+geKj$}$F+0`FahCmRC{0fbBSIFL&Mg;jA~aUEwnGyd?9v8dn8*(j z9@A^3KjN|ff*r(~?8-Ddiw-C&KVV-G^T>f)-8`HoHE& zvz8kz9r<^Ucs^!Ti{(a5JMq>hMv^*J6TOxj-WJ`uWo2h6LL=()r%_{_O>`D=ayw_` zWOo%4Rv0NFxZQ})T7VW=N;z{rb0M=rGABz}B9Gb9*CI`PwZ-tOi#74XM@DYe25z^W zSbpD-uj^<((cNjm=0^>f6ZQe+u_JmZf%%Cc&eV_{t? z+edUzVu}h{262fRd?L%e;3l$MhnA7$-cj|(qv{J$PLENdMv1@VS!Z;SnuKAYfi)~y zp~Q%}tBmHsJa%R`HG8ONYk%sa>PJSES4EXaMU}^)oF1b_4NkQO7i2>=uw#h!+yQHs zKXS~G4%{4LIJ72{7*+PbV@|{gy;j-w?`u%TaFZ>;o^uK{r(SSie=dTdpnTpEE{$q0 z<;Vd>m!&84FDkf56dp7j!3ft6qDqY@Uydq|s9~_f4!Uq~QMQ8JEvlT$u?V;CZk6rmT^?1wf|TL7I^hxZ;9%RQCo+Srg@S#n z<-neSoQIr%H6ET;WLbK`(*E;}Msq9LVc)Y*r=qX@sf;SGjw&yWDz}a*o6**z#-d}y z=EsV*9u;jpD%xQOw{vt+wq`dtsyu{)5pF-!D%;V%BdUBSDR(2<;St?M@mSh;&24?+ za8yvXm-c;@1A7LtSvu+)Mmt=Vp3q+|?d4hwmUh^+8SHTR3iwnb>djz>>!-j~WW5>e zaJ?C9N7PUp7_j-Vg6)V3wj(OoVF&kfa8b4<_h3}HkYf>UKgKHC!G1WZJk|_$ctqnk z*bZxaL}suZpmj7~%jilG-Nl@hxUxiEz+r7fvFiG0vsS5oQWYl&Kr zL8p{t8g>&=YuLY6lWEUjqV}ZI3u;r!8%A^q^e>=S-YX{0aduO(MfDtKt$H(wPUX(` z#OQg>3}uR#HV;};MVw#Y+$3_#oSDjtVrUt(>vgeyj`Kq?`c0?LaWf62LUBns^w;q} zCRRE>5Qi5y)0B(FxdqS`JH!pEom<446;8jBBX(3kTW5$SE1Z>L2{mwwA$HHX(jrvhBn_P-EI+Q z*I=BTMT@o2+NGNF+_TQvT)AF+zYf}yspcH#R6EmDHBGFq#yFo%Gbeff26Rpo(>9>t z7{BaXDGrxA-6D6R8NH1dSE}f<35U7jk{TR33h#S3REcvqsB15d=(KPPIvo-_wxIrQ z@%C06z7?-+b5=R}=Uhf4rpeuaLuPx3zETnqEJ%Jc-$Vf64sLR#3_B?xI zg}P4d(LOZv6yqIri@K@3jKg~|9(tQ*(G_Avg)`aMs_s_%b`V!Cb8b@Z7eCZETN!)Q z&(%F0WPB>cjqf{$D%-@;_np@U<8eNDCpDp`L91vKwUlY5!6%!tQP!w4=%H&sh5E8` z9oyG~I^D?j08k~YTC}+t)aW+QMZ;OSo0Sn@5aD$NDkQLn`u=P6nuuyvx!lo^2PXQWLhDf*K`)0lEqF(^ycUaSG0?(IY5$ zX(~(8K%I)%E(TSa$@Yt&MlXQ@Isp3d1C=gLgBqO!y_ASdrce^7Q!?8rpi0fzP6b<1 zcW@jn0S&5BDToLmY2tIz|WN)NGJ2x>GIbjb#&oxsY|U=qz@ zMSwcJ%62KJ(rmV02Q``p2Iwg0CkhbwvC;z6gOtvN3{a&^wl4-XY6k{r z6zHetSbv`NX`q+pvGN9}(*m{^f+{UydoifdQZPVupr4u=au9A%qa@Hv?N|x6$3dq{ zxX=kyDVyyaP@^tjK0N~ZNwEGB>#u@Hy{uG$I;~=RHK@{Bw%36gtq1e?C*~(VerO7I zB&lbDKDs=}(iNbN`v_Ux8&vs!11gjcy8}G{mQk}@u7AHI8fCgO#=FwxI zk6vJX8K}^5P@^gk3z?N|tZZldLr|xW*xn7Qw3qFVL5=puVg0-4C`*53sTNG4)2y5U zac9Ifc7AkLL4`C>BLfW3HK3nHus)LY`#>*E0Ck$g`ZH`l$M*A}N>kaM1_m{nfkPhc z0sZtNSDXMf`X}fk7yg^3PHs@4W^8-e_JJy;uzeAzQ5u*>cYuBx&w7yciJ*^O0(B~7 z&S92;$UpPBP$3Jn2vq4!wwHh!EeG@HN6=4+39?NRsF4r!Q5L9EHnR&e7gVS_+dbL7 z5>%--+W}CctH6Ak0{SVqfGZYq#bPj-Hi0^AVSOvx+u8mQRB0#MyFiWhfcfNYA{})F z73v0R)E!Kw8$q3JX5P*m0xC3=?Ej(|FS z&3Y}{-?9B)pi0Nst^+mt5zM15O{JfkL4|GsHM$-2(Sx8)4>!g7*Xd7Oc$_&NR4B;y z(`-)yu_3WN1?)jJ;OF$ZTmCNQgC1JL_6E>J+u7a?1}L$a9CTaIPuGA3^#}9l1<*(H zn_>O?X){-B=Zb_xS98e3;Pd&k)LHRi3(KDcrUge5)phE9}8f^eQw3n5SS=rC_r=U)sv;76A z(wA%>1~obgx~Pto6{DP5pOO~GVp6U0HM4qVZZ z?ccGT4dTrg+nqs;x`BE0DCnn`Sbv%IQqV`0tW<&6ir8KaVk=^M9oT{X2A0ut`0vdI zy#RXXjTEeZgDOyP(c4_H4h+y)&`Ztm?g058ME(Z@^eE`1X{;|{y^8gpSa&v;?c0L} z{SM5hhrwiej`a%Gmw|co1BeHnR9^pWQ)O7%ff{uLJ=BYp-mK)aeHExvU$*;!DqY9+ z^&swWKo|Xil_9L$33_NOE8{?&9%cJ6P^Bl>eiB6f58@D@cR@cL=8CUCjs6OH=@cuc zK|Iv6{WFNSZE14QDyWeI4A9k}pYCOS1nZ+gF9lhd0AfR8`x#KBDbfzovp8t<7cif8 zf_^$CE2xh3AHigDwvb93s8bWRn}RAeV>=Pl$P4DtZJ?hXVf|6op8$PS#7Z%!(=4_H zi2Ki%x$p{@N$bHGbVEyd{cZ+5G?MKHK^HyF_5?6M8$mDa1O4<**3Yuuvz62bf_{1! zH0V(d5)E-Y)=F6yJx!?#P6Q% z7eV~)LHzDP{O(yl&H6bIzx#CQKMBO`Iom1eLFuA77g9luT7m((6U62S;@%h3=xNYP zFR}76sMBj~&jwYR!}eTIqcSj`YC%86WJnLOphodvGG(yR8q}$62G&1TJ_;&z;EIl* zMxDSsDg^yBgY_cTXM#Rj#L8k&r)6v}2UV(My9(53CFr6}tkkfw1@zEotQ-V&`VtH( zbQlMfj&Q|MFq2|h%XJ_?gI)tYw1VwbV1VjDFC}Ek!Ce9x)ENxWIM7Sav0lOYGBBTh z1hIrKmhIbv$PHi~jRAc@n!*(exMB(DqBWpGYe9{wK@aU_We+Q#u)Pn|=`*$uf+~H% z_90NCufPChw2`OG192w}YIHT|r9ZGT1k@>b7Z(aZm4>r@H>lAFFhC1IKW$_Eebzq& zz4R3;M?l<+uw4tP^bOnJf*O4fy66-ur&&1%CXu_X98fb*CvT7oJ`gvPY+nSXP!2eQ zHi8Ci1wC|-?XN)0teqUhWuTvK0}UDq252_xRjlu2{Q&5qzkv#U4Qlia=%I70oNtHq zk5|X-Wwi?8L7MFtP$ehZaiB&CU;;H~C6$$ypqDzc(goBhm+kJLN>{Mm1JtM&7@!HD zpXRVWm-VvtSpQyH!_rz%r}b=a09D$^_9jrH&0v6x4$?&%P@%SpfSt^j>BfR%xuPPejs8>rGCwg-cmG#;El=Rt$?EL?~nrQl#tOE7^tvYi74=wZ-H zQ$Rm0V0{Ve$5_`ck$P*;p!Q%s-47@^b$I9MVlZe!d$k?}f9b2?jlIvXPpGXXI(5VHU=D-g47PiJGFp|kzkHFovi(}SOL itlTd0fQXe79<3_ukYx!HV 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 lZ`&a-s@hU zlWTvc#w53(1FnOLUJOEq!lnKx%TNg&`%R7@f``H>ZT*oRn;Dc@ua_RZ5*@yT>E8n5 zVoA@hWl;2P-%FdGUkl0%kV+=0*ZsM;Mq$mT)^_pOd!d5iB5xNmv|nnSEA=#9llZXP z*UR1V3fPwNSEEGa>Qs8Q!eez*zsvcwHSZpUv?{Gid>B%6&(-pZ!J{wfTQhdOyD7^2 zoPOIVChM}$Dz8>GAb9ZktRvOJSO>T|m^6x5{+_Y#^Y+-aYdtUCdZ=)kFHq`*K>LAZ zo5c@`6I;p`AJ2A=^32LgpL;#WP5j;O6wm9^m>YH06vkaFr8g#jGRD@s%=fLk zmAH-+W1CvnLMDH(-GDj$zVc?T8NNsJ=;6B?vuGVpU&uMqcm5dGTA9?|e5YrjDxjV2o0yltRGA9 zs6BgmL0Tj}n&##&QpaeZ*Wc`=g|CTuovfiKz>I13%k@x5PO^u4*nwxo&ZcymW(7A! z{OJMfSLWLU4ZkllGiGU7uZ2_rHhBwwU5k{FDrr4mIj#2k;y#6ip`K{N&+ zbNEQ};UlJuI0rsFqlv50x8I2?;3N1xQ5&IQNqn$8r-SH-0Xc=O#BHb zPp4Gim^hmHXe}(z*e?bUd%+Hr3kK_W# z1dL#332FRH{9es(DQz7B)dG5T|b*$mWGH0GVJmkh&Uh*aUL**cnB0joC{hZVn7>2Jm{daUp_#@ zf{*NS7epNBg(wds6VPtK5Z8eQh*m)J8UQL_Gek`g3{f9Efv5#Ai2zsw8;DxqBt!*} z0Z|rIK{Ns6>j10-P7n=16hwJY1W^wRU#FuTG?D<&1#S@a!8M2&kO&bEQrP7i5V0VQ zT~3FH16dH|K@G%1z#3yb#yQr{&%qz@PMcSA|Yym3W)kZAR8UP0HQSrVV6rFDu53VWq}wx-!TDp5LbfB z5Dh>bM0qd*Q4i=c(7sfNx*!pvK6nce1DYY?K^wc=4iO8me=(4q>qz?e)x9FQ{{!VF B12zBv diff --git a/Nextion_G4KLX/NX3224K028.tft b/Nextion_G4KLX/NX3224K028.tft index 28b552d13021644f2ac2deaa8bc40b8b84b65619..7880b8cb853f41da2c32c554bdef6b69de267da9 100644 GIT binary patch delta 5180 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 C&>q(S delta 14060 zcmds-3wRVow#QFDrjtn~VMt~sfdrU9z^&D z^l({;QRCuhV{9B9brH?>#*xQQ^j4QR%7eRqRyxUTh-We#_gzbq`>%fgtAWm%v5Okj zd=-~i^L_6t@h`rnNiSzj@}pW!#ba8{gBe;)RcEbcNo%d<>)y_q14GS!$EH!us2f); zWmiRoE%aBvYjp)D#Pm$Z?f+j;RCw(K_awJ6$*oRuYm?miB)4O=+Zf-y!tLIn17mbC z?rm*ciQC=Zv2)<|uCIwNZ&wA9mkIYSO_!sEU!MmDmnf-kLv}YBuzPE&sS-_a7mL8( z)e>ZMOqz9SaX&l5Jc}b8lxvXpl<-$*deOZ}+Kc@Ti8;r(LZ&*nR%E}YHx(arapa-Y z7`1ptQ9-)8HC4D@)n9>;o9T7h=&fSTIz2<%Gj^O%`f_X%T~F7}eA~?bZlLgQ(*4@jRF7!< zsxCw4jz4?2Gy5){5i1G?Ix^I<9NGSbt|xY%P4RitzHzwj5Hx7yJ2(3R?VfFW%EzY7g`d1vn`#+zT;l9by*Qg@mVTSBJV#ATl-~aFKn8 zI~VGMxM!fFrQL=2*Q_qMC*CJAe$q2k*O21Wvbb5N>Z~4!-JhT#5zTjno4>f987)0y zwb(p}gXxYtbl4n;kr)ZthY{gEcw7T^n{lokt8>Jbj}8w5`d|;fYe=XMKb0uoyntb7 z9+z$ne(?7LQkaJbC*Xa?vywOh5Aev{)eURiXa zg4mMdM|&b{PwXCmqw{=mq=%!hsIaLjCuyR3bwuZ~%+6h3RG23w_?#_e=i=?oRh5nq z1#KO{qC%I3;FR=j-aYQM{pK_?=l1ZT!j|e>cgc~vuf~)}wI;Tl68(w_ue74l9oGxb zX$~e|@7<+p&$#e>mUH2OqC!8$x336%s<%N~QseDuZOw^Rm@Fknp6gjM{f>0!;(mP| zd=-u!_B$w00;g$ZPg(%dCUs!}9mVbBj8dR?jaZiR8 zBH{lDcXuCiM~Dwku|8tYCm6wcJqWKw(M#zDYeTS?A#1N1cEeAlZS(#&M_JCb?c~7= zdn++_rRK-zUMK3tI{t{2U~6hg;Scv{CvhJKE^&;s?#Dl9^2XlV+VLlAleohc9<$cg z+iYR1J)PXF$i=|k&H99tqC#D+|8kB;Xog(#OGf^1y7(g>^%QJdc;|mU;XxA-heCIZbNqrxG?SP(~SL~$IR5!P->Kqa~68r3= zI`g>ibnXR6Tat747aX@mdER*@9eQQztW$0GMe5QH)MZOQ>s8M#aZj4l>!>?c_owqv z-qO2QXMV|=cqGl~&|}YRknUHid$xvDgR``UxM53T*YB??b!{y;9?p-!C9I|{j5D|N z;?lPJ!Uas3G2 zb78Q8a%ICcZ^r|@`KsZ*c#BfMJ7dQ$tzj2;CWarLr(LvzS%L1{aQ#8p-0ZjGU)bEP zJ$*_>GP_|1_qgm&0V985-&=g4W&0kZ9xv*S`Aw^T>s(FV@CG8Ne81_Z!K`CKmXj|^eMzM zi2d@vykcuj*oEwXwf&|y+<-9=-!~c_!PJHwJdP(9>l4DoBdD1hsJG$jPhtQESZ~F@ zFo5md=YGEE*I+QlVKfg|A6ByJJ>+yiFytm!-&ccc{ zm=}+&9}lXQj|Lq)AMt!ye{3_Xv6UVgtUe`SaIO91x$~kY&!+N0x52v8f9-kHS9Y*^ zuf2%bXI{)^gkI9!OCz4xqn{rgXg-&p8L|GY;Mjqb<*~ zekJ(kqqpgDHs+uGo?k!4>{K(`Iqz3PX?j{15wghT+;XuLp|MtLJ2aujF0U7c3H&1A zF};@iBVPM2+Ci+&u1>dC(Z2J_4%#DPo;h%9n}(N3*~@Sd&VI)i{{DoSf*8t!v))az zzdwzf7t60~EdMpp{)$ol&i2zRZHqBq{%6`B7jxxjw~q#>ui&MTzjueRSXnc^k8$sy znJbOvj{Kp+UW{4OY^AZat$6EWBUzoIi5@ErZ?nrU%gWADghq7jqa#K;6Ll7Hayw<^ zWOotcR~e}yxYLNwT7VK+N*QxLb0Kq)WKNc{OrEo)uf>}9a+~2-muTY0kBr=`O{c*45mNliSm%eX?9aT0sC8%@<15;+HqO!2!a<1P`u*6?|! zpq4W>HYY2`shD9x+{CgSMw9$sjZUZf(UF%%oV#S@bpD-f5uQDho_H{+i?%d~2xV_{t;%STjD zVu}iy2QfPJev#=Oa1)uXMajr?(4QTc^PXT&H`tyDs-bdj2jW}$%9EZJ-&M$B1b zG!0(O&ZH9lsVHmzeu5F19v+om6O|qjl^%n1MvNNOIMr@kkPTVDjv>l(1ti zaC3~|(CSQ5RN4cNIT081T4~$AuU;C>O|b;K&#BaudccAGYXU<-`hq1~7FAx#kqwMa zOHZg@RM12e9x@!k2-goION~fhi8e&0r$^;q9+lrJD&GvY78Ps_4K{!B9L$K(qJpha z{a}Y3bmrh9Z3X-CsB|vJB3!0FxwDg3j{md;!Q!CnG-!qY?qOScbk4mqNN-vK}w~9)e(bl8dqNBy;&x*Dl z6>U8#+F=KGa&(cldN(jCJ&1!5E;b~xV*wj-)3 z4m8;OS;2Nh1=|r7?68BUIJih#of{XG9?!7|mw(1e+rgd?m44O?cDO|oDIV*H!)hN< z94rNCdmVYna$xsB7R*3?{a}aF(i7^-U}rmG9Q`6(Z$y?Fkv4-JzVwl({6SG^GuV#F z!y3onU+kJ;jh!D)yZy3`L=K9fmnn@}b|!M63t37VO6pGJgZ_q+MiY7yH6w#gD=9SU zR-#t0f3K#{(SbzmNT-+7M%3?KqD!HF4t>l0V$y8q0;w-=ZE5frB0utKMkfracLR!H}Ib&Ry#is zM;18Kl}p6=1<;l_#La7++r-jEPQQ{Pb}fRo!4Qe7oaJKp5@%;+rFeY_w7Iz)EiRiup1(&(R?nNkAvl3cBB&>2)D|^I0G-JQex*=pB%WRk>1D5UyF+wb>uj#H5PjA{o9~luw~2G>(9TYx z*?MSg)69PE+u&@f+$6r=0PU$XvyZbYo$0EYE;d%8ozJJ6o&4h_R8A68H=*E2zpPv? zj+8mwB6qVHz0GJ>nz(igPIJViTXE_jynn^1LY&7*U4KbLrG?v2>9E+f4f#XG+uL#a zR=mE$S>foDa|Mx@I@{U7n5CAfU$>RDwmId~!%u`~0@3%dk!&nbSE_OCxc9~?b%WZi zU8w1)#yjdZbxS+h{G-Npb+6j1eW(lR#y<5kbzgfKkaBU$`_9424zc`wXYXJQf_~hQ z(#082qw}DblCX3slnm;W!geaCQd73mz~8; za#kupoz}3u7F20H+Z#ZQHiCJ4i}}fq8%@C$B=tm&xp&pvwRAPoaF+ z?dc(K9yN}YGav=@PzSbiKo@mqJ0A?tM9@pGf__@Z`X<&1|EbMOUeHfnK!dIV1N4*= z^WRHFoUw{C)`0nR227#Gak4@VXwVg49z6m2=q1)yfC{YyHL3tHky+Wn%1*XF1a`5Bg~s>%&=p z5cJY`P^XEkKgae9Y`+MqG==S{U{Is!IOWkk&`&2g<0PojKS3Y4@Lw`@a)SyrX4}iQ z4^%0Y?Ixf`>0ln+1^Q_m>p|8hfIfN!)Txv?n>i1}`ZJ#si)4ZpgDNd$dl{(FN-&R3 zfPP9!kY$oVjeMYwvOt}(nVp%rph8{Q?#}krph`X24uBe61Lo6Y&`-eyoUxEImVkJI z0Cn2N`gXQ=vi%{b(r&i*fEw)s^U0ei9d!W}x*XJ~E0{vJfI9VO-pL#UDm0kw0=DlB z;-u10PTU7-G#m`jBG6Cov;G0=yFf1;1$FwG^=h`iWBb2Am40Bm7S!kjm`9x(Nk9ET zg>C~ix)b!#!=O&18e#tH^e88uWR3$B3bOqy+Y>=-NNi6AyU|wgGkU`I~3N0nni9!F+m=^*O9>W_>#tpjeM=P&3d^-NB$i`8eg#bD)p@ z!WkPth28-*+5~#2ij}{ya)9kmK%G8g`*TpGFW5c;YIF>AQ7tRSSvd(Nli`(~V?b=v z(#HLd6ZUD&Xar)P2C+|r*r!<^!}?<&wrN(Tg4m|nE(Wnpv;8uNZ5qTj4Pu*S{S52p zL2T1LS=R?*pi?-J#)))LrRHq605xg}251QAr)OE8$ogc^OQo#54(c?A?YW>z^Vpsb zYP1l{r|&>N#iz(ixh_`sQe+Kb+EnPNR1vPSj0lE(K)BUUu zWBmcpOF>q~gV>PRehyS=vb2NrJWd+@8O*2Mpr3w_8C1*q2{476&7=|s>XgWKBT%Kr zY$t&ldBHro1N75ltUu2BSkOmBtQ3Pf&174Ec>a8q6R&}pv=N+6H#e8#*B|uIaJC-? zUGyZ|ptFeL;<$1-2j){X=%<*L z(nBn$Q9PJJEm>&=>eQws=09dW5-PRlj1Hhi9l<;*1pPFf^&-}1fIeEx$`Vkg6>P5r zRVrt@0@P?V=%OvGY-ME|=%G(pIRxtT1sGK52u>;;<&0xsCdIUp^FV+Gy$*V4729jT z0M&tBO30LryA(926BwW|pqE}?eG%&`zG|*4WSziHS{V&HU zh2CfB15l^kZ0`Y8+ROGnP@|8*JWA;xU0eYwlnZKf73ibBtn>qQx}EJiK$QltJrK;K zao}`H%#uUq0X;!#%ZZL)0$s&+01VIs&`SdJ(;C(*S--$~@}*Mm1RB&8%%`Wo6e?l8 zg7tM^9-Re!)VQN8p93l+KRX_G$PbRkV>9Kq#=->omGOA2{JvNiBfl&b#>j7qh5DBJ Rr7iX!bmr6pIoK5M4SjLQAli|h+? z&+?Sz?%h(BYibt*#KpOPYzuQm1S)g?0Pz9N%G{X3%G?!fmAQs?g}Fvh43Y=2r#s4V zw6Hj)@qFGqk?}f9b2?jlIvXPpGXXI(5VHU=D-g47PiJGFp|kzkHFovi(}SOLtlTd0 ef@09 zIQG19F!GZ)tmph03iE$MF|G%q(L``r=~`oF7iZ|<_U7FIH!CqOH(i6}@ZRfoSl7eN z>4lfuoEk4T`-fg`8Vfz$W

A&Z;?@rsnsItK6 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

r0nF*OG(Vvy3o9aqLpH$ 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 diff --git a/Nextion_G4KLX/NX4832T035.HMI b/Nextion_G4KLX/NX4832T035.HMI index cda3ced11aa48fa6db64601e58bd279a83d92eac..3128178144d9cc1c5ea99b764a4462eb3b538832 100644 GIT binary patch delta 294 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 r$H>>fdGYcE@c9_|>Nn3OU;5_R -class CNullModem : public IModem { + +class CNullModem : public CModem { public: - CNullModem(); + CNullModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); virtual ~CNullModem(); - virtual void setSerialParams(const std::string& protocol, unsigned int address, unsigned int speed) {}; - 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 m17Enabled, bool pocsagEnabled, bool fmEnabled, bool ax25Enabled) {}; - virtual void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagLevel, float fmTXLevel, float ax25TXLevel) {}; - 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 setAX25Params(int rxTwist, unsigned int txDelay, unsigned int slotTime, unsigned int pPersist) {}; - 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) {}; - virtual void setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel) {}; - virtual void setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, bool noiseSquelch, unsigned int squelchHighThreshold, unsigned int squelchLowThreshold, unsigned int rfAudioBoost, float maxDevLevel) {}; - virtual void setFMExtParams(const std::string& ack, unsigned int audioBoost) {}; + 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 setDMRParams(unsigned int colorCode){}; + virtual void setYSFParams(bool loDev, unsigned int txHang){}; + virtual void setTransparentDataParams(unsigned int sendFrameType){}; virtual bool open(); - virtual unsigned int readDStarData(unsigned char* data) { return 0U; }; - virtual unsigned int readDMRData1(unsigned char* data) { return 0U; }; - virtual unsigned int readDMRData2(unsigned char* data) { return 0U; }; - virtual unsigned int readYSFData(unsigned char* data) { return 0U; }; - virtual unsigned int readP25Data(unsigned char* data) { return 0U; }; - virtual unsigned int readNXDNData(unsigned char* data) { return 0U; }; - virtual unsigned int readM17Data(unsigned char* data) { return 0U; }; - virtual unsigned int readFMData(unsigned char* data) { return 0U; }; - virtual unsigned int readAX25Data(unsigned char* data) { return 0U; }; + virtual unsigned int readDStarData(unsigned char* data){return 0;}; + virtual unsigned int readDMRData1(unsigned char* data){return 0;}; + virtual unsigned int readDMRData2(unsigned char* data){return 0;}; + virtual unsigned int readYSFData(unsigned char* data){return 0;}; + virtual unsigned int readP25Data(unsigned char* data){return 0;}; + virtual unsigned int readNXDNData(unsigned char* data){return 0;}; + virtual unsigned int readTransparentData(unsigned char* data){return 0;}; - virtual bool hasDStarSpace()const { return true; }; - virtual bool hasDMRSpace1() const { return true; }; - virtual bool hasDMRSpace2() const { return true; }; - virtual bool hasYSFSpace() const { return true; }; - virtual bool hasP25Space() const { return true; }; - virtual bool hasNXDNSpace() const { return true; }; - virtual bool hasM17Space() const { return true; }; - virtual bool hasPOCSAGSpace() const { return true; }; - virtual unsigned int getFMSpace() const { return true; }; - virtual bool hasAX25Space() const { return true; }; + virtual unsigned int readSerial(unsigned char* data, unsigned int length){return 0;}; - virtual bool hasTX() const { return false; }; - virtual bool hasCD() const { return false; }; + virtual bool hasDStarSpace()const {return true;}; + virtual bool hasDMRSpace1() const {return true;}; + virtual bool hasDMRSpace2() const {return true;}; + virtual bool hasYSFSpace() const {return true;}; + virtual bool hasP25Space() const {return true;}; + virtual bool hasNXDNSpace() const {return true;}; + virtual bool hasPOCSAGSpace() const{return true;}; - virtual bool hasLockout() const { return false; }; - virtual bool hasError() const { return false; }; + virtual bool hasTX() const {return false;}; + virtual bool hasCD() const {return false;}; - virtual bool writeDStarData(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeDMRData1(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeDMRData2(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeYSFData(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeP25Data(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeNXDNData(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeM17Data(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writePOCSAGData(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeFMData(const unsigned char* data, unsigned int length) { return true; }; - virtual bool writeAX25Data(const unsigned char* data, unsigned int length) { return true; }; + virtual bool hasLockout() const {return false;}; + virtual bool hasError() const {return false;}; - virtual bool writeConfig() { return true; }; - virtual bool writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) { return true; }; - virtual bool writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type) { return true; }; - virtual bool writeYSFInfo(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin) { return true; }; - virtual bool writeP25Info(const char* source, bool group, unsigned int dest, const char* type) { return true; }; - virtual bool writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type) { return true; }; - virtual bool writeM17Info(const char* source, const char* dest, const char* type) { return true; }; - virtual bool writePOCSAGInfo(unsigned int ric, const std::string& message) { return true; }; - virtual bool writeIPInfo(const std::string& address) { return true; }; + virtual bool writeDStarData(const unsigned char* data, unsigned int length){return true;}; + virtual bool writeDMRData1(const unsigned char* data, unsigned int length){return true;}; + virtual bool writeDMRData2(const unsigned char* data, unsigned int length){return true;}; + virtual bool writeYSFData(const unsigned char* data, unsigned int length){return true;}; + virtual bool writeP25Data(const unsigned char* data, unsigned int length){return true;}; + virtual bool writeNXDNData(const unsigned char* data, unsigned int length){return true;}; + virtual bool writePOCSAGData(const unsigned char* data, unsigned int length){return true;}; - virtual bool writeDMRStart(bool tx) { return true; }; - virtual bool writeDMRShortLC(const unsigned char* lc) { return true; }; - virtual bool writeDMRAbort(unsigned int slotNo) { return true; }; + virtual bool writeTransparentData(const unsigned char* data, unsigned int length){return true;}; - virtual bool writeTransparentData(const unsigned char* data, unsigned int length) { return true; }; - virtual unsigned int readTransparentData(unsigned char* data) { return 0U; }; + virtual bool writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector){return true;}; + virtual bool writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type){return true;}; + virtual bool writeYSFInfo(const char* source, const char* dest, const char* type, const char* origin){return true;}; + virtual bool writeP25Info(const char* source, bool group, unsigned int dest, const char* type){return true;}; + virtual bool writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type){return true;}; + virtual bool writePOCSAGInfo(unsigned int ric, const std::string& message){return true;}; + virtual bool writeIPInfo(const std::string& address){return true;}; - virtual bool writeSerial(const unsigned char* data, unsigned int length) { return true; }; - virtual unsigned int readSerial(unsigned char* data, unsigned int length) { return 0U; }; + virtual bool writeDMRStart(bool tx){return true;}; + virtual bool writeDMRShortLC(const unsigned char* lc){return true;}; + virtual bool writeDMRAbort(unsigned int slotNo){return true;}; - virtual unsigned char getMode() const { return MODE_IDLE; }; - virtual bool setMode(unsigned char mode) { return true; }; + virtual bool writeSerial(const unsigned char* data, unsigned int length){return true;}; - virtual bool sendCWId(const std::string& callsign) { return true; }; + virtual bool setMode(unsigned char mode){return true;}; - virtual HW_TYPE getHWType() const { return HWT_MMDVM; }; + virtual bool sendCWId(const std::string& callsign){return true;}; - virtual void clock(unsigned int ms) {}; + virtual HW_TYPE getHWType() const {return m_hwType;}; - virtual void close() {}; + virtual void clock(unsigned int ms){}; + + virtual void close(){}; private: + HW_TYPE m_hwType; }; #endif diff --git a/OLED.cpp b/OLED.cpp index 0b765d4..562afde 100644 --- a/OLED.cpp +++ b/OLED.cpp @@ -148,28 +148,6 @@ const unsigned char logo_NXDN_bmp [] = 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; -// Logo M17_sm, 128x16px -// XXX FIXME This is the NXDN logo, it needs replacing with the M17 logo -const unsigned char logo_M17_bmp [] = -{ -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xf0, 0x1f, 0xf8, 0x0f, 0x00, 0xff, 0x80, 0x7c, 0x00, 0x0f, 0xff, 0x80, 0x7f, 0xe0, 0x7f, -0xff, 0xe0, 0x0f, 0xf0, 0x1f, 0x80, 0x7e, 0x01, 0xf8, 0x00, 0x00, 0x7f, 0x00, 0x3f, 0xc0, 0x7f, -0xff, 0xc0, 0x07, 0xe0, 0x3f, 0x80, 0x38, 0x07, 0xf0, 0x00, 0x00, 0x3e, 0x00, 0x3f, 0x80, 0xff, -0xff, 0x80, 0x03, 0xc0, 0x3f, 0xc0, 0x00, 0x3f, 0xe0, 0x1f, 0x80, 0x3e, 0x00, 0x1f, 0x01, 0xff, -0xff, 0x00, 0x03, 0x80, 0x7f, 0xe0, 0x00, 0xff, 0xc0, 0x3f, 0x80, 0x3c, 0x00, 0x0e, 0x03, 0xff, -0xfe, 0x00, 0x01, 0x00, 0xff, 0xe0, 0x03, 0xff, 0x80, 0x7f, 0x80, 0x78, 0x08, 0x04, 0x03, 0xff, -0xfc, 0x03, 0x00, 0x01, 0xff, 0x80, 0x01, 0xff, 0x00, 0xff, 0x00, 0xf0, 0x1c, 0x00, 0x07, 0xff, -0xfc, 0x07, 0x80, 0x03, 0xfc, 0x00, 0x01, 0xfe, 0x01, 0xfc, 0x01, 0xe0, 0x1e, 0x00, 0x0f, 0xff, -0xf8, 0x0f, 0xc0, 0x07, 0xf0, 0x0e, 0x00, 0xfc, 0x00, 0x00, 0x07, 0xc0, 0x3f, 0x00, 0x1f, 0xff, -0xf0, 0x1f, 0xe0, 0x0f, 0x80, 0x3f, 0x00, 0x7c, 0x00, 0x00, 0x3f, 0xc0, 0x7f, 0x80, 0x3f, 0xff, -0xe0, 0x3f, 0xf0, 0x0e, 0x01, 0xff, 0x80, 0x38, 0x00, 0x07, 0xff, 0x80, 0xff, 0x80, 0x7f, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; - // Logo POCASG/DAPNET, 128x16px const unsigned char logo_POCSAG_bmp [] = { @@ -368,6 +346,7 @@ void COLED::writeDStarInt(const char* my1, const char* my2, const char* your, co OLED_statusbar(); m_display.display(); + } void COLED::clearDStarInt() @@ -521,6 +500,7 @@ void COLED::writeP25Int(const char* source, bool group, unsigned int dest, const OLED_statusbar(); m_display.display(); + } void COLED::clearP25Int() @@ -586,36 +566,6 @@ void COLED::clearNXDNInt() m_display.display(); } -void COLED::writeM17Int(const char* source, const char* dest, const char* type) -{ - m_mode = MODE_M17; - - m_display.clearDisplay(); - m_display.fillRect(0, OLED_LINE2, m_display.width(), m_display.height(), BLACK); - - m_display.setCursor(0,OLED_LINE3); - m_display.printf("%s %s", type, source); - - m_display.setCursor(0,OLED_LINE4); - m_display.printf(" %s", dest); - - OLED_statusbar(); - m_display.display(); -} - -void COLED::clearM17Int() -{ - m_display.fillRect(0, OLED_LINE2, m_display.width(), m_display.height(), BLACK); - - m_display.setCursor(40,OLED_LINE4); - m_display.print("Listening"); - - m_display.setCursor(0,OLED_LINE6); - m_display.printf("%s",m_ipaddress.c_str()); - - m_display.display(); -} - void COLED::writePOCSAGInt(uint32_t ric, const std::string& message) { m_mode = MODE_POCSAG; @@ -644,6 +594,7 @@ void COLED::writePOCSAGInt(uint32_t ric, const std::string& message) OLED_statusbar(); m_display.display(); + } void COLED::clearPOCSAGInt() @@ -718,8 +669,6 @@ void COLED::OLED_statusbar() m_display.drawBitmap(0, 0, logo_P25_bmp, 128, 16, WHITE); else if (m_mode == MODE_NXDN) m_display.drawBitmap(0, 0, logo_NXDN_bmp, 128, 16, WHITE); - else if (m_mode == MODE_M17) - m_display.drawBitmap(0, 0, logo_M17_bmp, 128, 16, WHITE); else if (m_mode == MODE_POCSAG) m_display.drawBitmap(0, 0, logo_POCSAG_bmp, 128, 16, WHITE); else if (m_displayLogoScreensaver) diff --git a/OLED.h b/OLED.h index a7ab811..5f3ead0 100644 --- a/OLED.h +++ b/OLED.h @@ -66,16 +66,10 @@ public: virtual void writeP25Int(const char* source, bool group, unsigned int dest, const char* type); virtual void clearP25Int(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void clearM17Int(); - virtual void writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type); virtual int writeNXDNIntEx(const class CUserDBentry& source, bool group, unsigned int dest, const char* type); virtual void clearNXDNInt(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void clearM17Int(); - virtual void writePOCSAGInt(uint32_t ric, const std::string& message); virtual void clearPOCSAGInt(); diff --git a/PseudoTTYController.cpp b/PseudoTTYController.cpp deleted file mode 100644 index 52a4ac9..0000000 --- a/PseudoTTYController.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2002-2004,2007-2011,2013,2014-2017,2019,2020 by Jonathan Naylor G4KLX - * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX - * - * 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(_WIN32) && !defined(_WIN64) - -#include "PseudoTTYController.h" -#include "Log.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -CPseudoTTYController::CPseudoTTYController(const std::string& symlink, unsigned int speed, bool assertRTS) : -CSerialController(speed, assertRTS), -m_symlink(symlink) -{ -} - -CPseudoTTYController::~CPseudoTTYController() -{ -} - -bool CPseudoTTYController::open() -{ - assert(m_fd == -1); - - int slavefd; - char slave[300]; - int result = ::openpty(&m_fd, &slavefd, slave, NULL, NULL); - if (result < 0) { - LogError("Cannot open the pseudo tty - errno : %d", errno); - return false; - } - - // Remove any previous stale symlink - ::unlink(m_symlink.c_str()); - - int ret = ::symlink(slave, m_symlink.c_str()); - if (ret != 0) { - LogError("Cannot make symlink to %s with %s", slave, m_symlink.c_str()); - close(); - return false; - } - - LogMessage("Made symbolic link from %s to %s", slave, m_symlink.c_str()); - - m_device = std::string(::ttyname(m_fd)); - - return setRaw(); -} - -void CPseudoTTYController::close() -{ - CSerialController::close(); - - ::unlink(m_symlink.c_str()); -} - -#endif - diff --git a/PseudoTTYController.h b/PseudoTTYController.h deleted file mode 100644 index ccf6ba1..0000000 --- a/PseudoTTYController.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 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 PseudoTTYController_H -#define PseudoTTYController_H - -#if !defined(_WIN32) && !defined(_WIN64) - -#include - -#include "SerialController.h" - -class CPseudoTTYController : public CSerialController { -public: - CPseudoTTYController(const std::string& symlink, unsigned int speed, bool assertRTS = false); - virtual ~CPseudoTTYController(); - - virtual bool open(); - - virtual void close(); - -protected: - std::string m_symlink; -}; - -#endif - -#endif diff --git a/README.md b/README.md index 0b6fe0b..fafc4bf 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ These are the source files for building the MMDVMHost, the program that interfaces to the MMDVM or DVMega on the one side, and a suitable network on -the other. It supports D-Star, DMR, P25 Phase 1, NXDN, System Fusion, M17, -POCSAG, FM, and AX.25 on the MMDVM, and D-Star, DMR, and System Fusion on the DVMega. +the other. It supports D-Star, DMR, P25 Phase 1, NXDN, System Fusion, +POCSAG, and FM on the MMDVM, and D-Star, DMR, and System Fusion on the DVMega. On the D-Star side the MMDVMHost interfaces with the ircDDB Gateway, on DMR it connects to the DMR Gateway to allow for connection to multiple DMR networks, on System Fusion it connects to the YSF Gateway to allow access to the FCS and YSF networks. On P25 it connects to the P25 Gateway. On NXDN it connects to the NXDN Gateway which provides access to the NXDN and -NXCore talk groups. On M17 it uses the M17 Gateway to access the M17 reflector system. -It uses the DAPNET Gateway to access DAPNET to receive +NXCore talk groups. It uses the DAPNET Gateway to access DAPNET to receive paging messages. Finally it uses the FM Gateway to interface to existing FM networks. @@ -27,7 +26,8 @@ these are: The Nextion displays can connect to the UART on the Raspberry Pi, or via a USB to TTL serial converter like the FT-232RL. It may also be connected to the UART -output of the MMDVM modem (Arduino Due, STM32, Teensy). +output of the MMDVM modem (Arduino Due, STM32, Teensy), or to the UART output +on the UMP. The HD44780 displays are integrated with wiringPi for Raspberry Pi based platforms. diff --git a/RemoteControl.cpp b/RemoteControl.cpp index af58dc5..3c2d2a8 100644 --- a/RemoteControl.cpp +++ b/RemoteControl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2019 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,8 +89,6 @@ 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; @@ -102,12 +100,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(1U) == "ax25") - m_command = RCD_ENABLE_AX25; } else if (m_args.at(0U) == "disable" && m_args.size() >= DISABLE_ARGS) { if (m_args.at(1U) == "dstar") m_command = RCD_DISABLE_DSTAR; @@ -119,12 +113,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(1U) == "ax25") - m_command = RCD_DISABLE_AX25; } else if (m_args.at(0U) == "page" && m_args.size() >= PAGE_ARGS) { // Page command is in the form of "page " m_command = RCD_PAGE; @@ -154,7 +144,6 @@ 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; @@ -175,15 +164,14 @@ 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 b4aea18..c8d060d 100644 --- a/RemoteControl.h +++ b/RemoteControl.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2019 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,24 +33,19 @@ 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_ENABLE_AX25, RCD_DISABLE_DSTAR, RCD_DISABLE_DMR, RCD_DISABLE_YSF, RCD_DISABLE_P25, RCD_DISABLE_NXDN, - RCD_DISABLE_M17, RCD_DISABLE_FM, - RCD_DISABLE_AX25, RCD_PAGE, RCD_CW }; diff --git a/SerialController.cpp b/SerialController.cpp index 99fe576..cfb448d 100644 --- a/SerialController.cpp +++ b/SerialController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2002-2004,2007-2011,2013,2014-2017,2019,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2002-2004,2007-2011,2013,2014-2017,2019 by Jonathan Naylor G4KLX * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX * * This program is free software; you can redistribute it and/or modify @@ -23,11 +23,12 @@ #include #include +#include + #if defined(_WIN32) || defined(_WIN64) #include #include #else -#include #include #include #include @@ -39,7 +40,7 @@ #if defined(_WIN32) || defined(_WIN64) -CSerialController::CSerialController(const std::string& device, unsigned int speed, bool assertRTS) : +CSerialController::CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : m_device(device), m_speed(speed), m_assertRTS(assertRTS), @@ -48,14 +49,6 @@ m_handle(INVALID_HANDLE_VALUE) assert(!device.empty()); } -CSerialController::CSerialController(unsigned int speed, bool assertRTS) : -m_device(), -m_speed(speed), -m_assertRTS(assertRTS), -m_handle(INVALID_HANDLE_VALUE) -{ -} - CSerialController::~CSerialController() { } @@ -228,7 +221,7 @@ void CSerialController::close() #else -CSerialController::CSerialController(const std::string& device, unsigned int speed, bool assertRTS) : +CSerialController::CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : m_device(device), m_speed(speed), m_assertRTS(assertRTS), @@ -237,14 +230,6 @@ m_fd(-1) assert(!device.empty()); } -CSerialController::CSerialController(unsigned int speed, bool assertRTS) : -m_device(), -m_speed(speed), -m_assertRTS(assertRTS), -m_fd(-1) -{ -} - CSerialController::~CSerialController() { } @@ -263,106 +248,96 @@ bool CSerialController::open() return false; } - if (::isatty(m_fd)) - return setRaw(); - - return true; -} + if (::isatty(m_fd)) { + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + LogError("Cannot get the attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } -bool CSerialController::setRaw() -{ - termios termios; - if (::tcgetattr(m_fd, &termios) < 0) { - LogError("Cannot get the attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - - termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); - termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); - termios.c_iflag &= ~(IXON | IXOFF | IXANY); - termios.c_oflag &= ~(OPOST); - termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); - termios.c_cflag |= (CS8 | CLOCAL | CREAD); - termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); - termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); + termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); + termios.c_iflag &= ~(IXON | IXOFF | IXANY); + termios.c_oflag &= ~(OPOST); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= (CS8 | CLOCAL | CREAD); + termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); + termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); #if defined(__APPLE__) - termios.c_cc[VMIN] = 1; - termios.c_cc[VTIME] = 1; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 1; #else - termios.c_cc[VMIN] = 0; - termios.c_cc[VTIME] = 10; + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; #endif - switch (m_speed) { - case 1200U: - ::cfsetospeed(&termios, B1200); - ::cfsetispeed(&termios, B1200); - break; - case 2400U: - ::cfsetospeed(&termios, B2400); - ::cfsetispeed(&termios, B2400); - break; - case 4800U: - ::cfsetospeed(&termios, B4800); - ::cfsetispeed(&termios, B4800); - break; - case 9600U: - ::cfsetospeed(&termios, B9600); - ::cfsetispeed(&termios, B9600); - break; - case 19200U: - ::cfsetospeed(&termios, B19200); - ::cfsetispeed(&termios, B19200); - break; - case 38400U: - ::cfsetospeed(&termios, B38400); - ::cfsetispeed(&termios, B38400); - break; - case 115200U: - ::cfsetospeed(&termios, B115200); - ::cfsetispeed(&termios, B115200); - break; - case 230400U: - ::cfsetospeed(&termios, B230400); - ::cfsetispeed(&termios, B230400); - break; - case 460800U: - ::cfsetospeed(&termios, B460800); - ::cfsetispeed(&termios, B460800); - break; - default: - LogError("Unsupported serial port speed - %u", m_speed); - ::close(m_fd); - return false; - } + switch (m_speed) { + case SERIAL_1200: + ::cfsetospeed(&termios, B1200); + ::cfsetispeed(&termios, B1200); + break; + case SERIAL_2400: + ::cfsetospeed(&termios, B2400); + ::cfsetispeed(&termios, B2400); + break; + case SERIAL_4800: + ::cfsetospeed(&termios, B4800); + ::cfsetispeed(&termios, B4800); + break; + case SERIAL_9600: + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + break; + case SERIAL_19200: + ::cfsetospeed(&termios, B19200); + ::cfsetispeed(&termios, B19200); + break; + case SERIAL_38400: + ::cfsetospeed(&termios, B38400); + ::cfsetispeed(&termios, B38400); + break; + case SERIAL_115200: + ::cfsetospeed(&termios, B115200); + ::cfsetispeed(&termios, B115200); + break; + case SERIAL_230400: + ::cfsetospeed(&termios, B230400); + ::cfsetispeed(&termios, B230400); + break; + default: + LogError("Unsupported serial port speed - %d", int(m_speed)); + ::close(m_fd); + return false; + } - if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { - LogError("Cannot set the attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - - if (m_assertRTS) { - unsigned int y; - if (::ioctl(m_fd, TIOCMGET, &y) < 0) { - LogError("Cannot get the control attributes for %s", m_device.c_str()); + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + LogError("Cannot set the attributes for %s", m_device.c_str()); ::close(m_fd); return false; } - y |= TIOCM_RTS; + if (m_assertRTS) { + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + LogError("Cannot get the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } - if (::ioctl(m_fd, TIOCMSET, &y) < 0) { - LogError("Cannot set the control attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + LogError("Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } } - } #if defined(__APPLE__) - setNonblock(false); + setNonblock(false); #endif + } return true; } diff --git a/SerialController.h b/SerialController.h index e4adb7d..6dfc461 100644 --- a/SerialController.h +++ b/SerialController.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2002-2004,2007-2009,2011-2013,2015-2017,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2002-2004,2007-2009,2011-2013,2015-2017 by Jonathan Naylor G4KLX * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX * * This program is free software; you can redistribute it and/or modify @@ -28,9 +28,21 @@ #include #endif +enum SERIAL_SPEED { + SERIAL_1200 = 1200, + SERIAL_2400 = 2400, + SERIAL_4800 = 4800, + SERIAL_9600 = 9600, + SERIAL_19200 = 19200, + SERIAL_38400 = 38400, + SERIAL_76800 = 76800, + SERIAL_115200 = 115200, + SERIAL_230400 = 230400 +}; + class CSerialController : public ISerialPort { public: - CSerialController(const std::string& device, unsigned int speed, bool assertRTS = false); + CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false); virtual ~CSerialController(); virtual bool open(); @@ -46,10 +58,8 @@ public: #endif protected: - CSerialController(unsigned int speed, bool assertRTS = false); - std::string m_device; - unsigned int m_speed; + SERIAL_SPEED m_speed; bool m_assertRTS; #if defined(_WIN32) || defined(_WIN64) HANDLE m_handle; @@ -61,7 +71,6 @@ protected: int readNonblock(unsigned char* buffer, unsigned int length); #else bool canWrite(); - bool setRaw(); #endif }; diff --git a/SerialModem.cpp b/SerialModem.cpp deleted file mode 100644 index 721c736..0000000 --- a/SerialModem.cpp +++ /dev/null @@ -1,2869 +0,0 @@ -/* - * Copyright (C) 2011-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. - */ - -#include "SerialController.h" -#if defined(__linux__) -#include "I2CController.h" -#endif -#include "DStarDefines.h" -#include "DMRDefines.h" -#include "YSFDefines.h" -#include "P25Defines.h" -#include "NXDNDefines.h" -#include "AX25Defines.h" -#include "POCSAGDefines.h" -#include "M17Defines.h" -#include "Thread.h" -#include "SerialModem.h" -#include "NullModem.h" -#include "Utils.h" -#include "Log.h" - -#include -#include -#include -#include -#include - -#if defined(_WIN32) || defined(_WIN64) -#include -#else -#include -#endif - -const unsigned char MMDVM_FRAME_START = 0xE0U; - -const unsigned char MMDVM_GET_VERSION = 0x00U; -const unsigned char MMDVM_GET_STATUS = 0x01U; -const unsigned char MMDVM_SET_CONFIG = 0x02U; -const unsigned char MMDVM_SET_MODE = 0x03U; -const unsigned char MMDVM_SET_FREQ = 0x04U; - -const unsigned char MMDVM_SEND_CWID = 0x0AU; - -const unsigned char MMDVM_DSTAR_HEADER = 0x10U; -const unsigned char MMDVM_DSTAR_DATA = 0x11U; -const unsigned char MMDVM_DSTAR_LOST = 0x12U; -const unsigned char MMDVM_DSTAR_EOT = 0x13U; - -const unsigned char MMDVM_DMR_DATA1 = 0x18U; -const unsigned char MMDVM_DMR_LOST1 = 0x19U; -const unsigned char MMDVM_DMR_DATA2 = 0x1AU; -const unsigned char MMDVM_DMR_LOST2 = 0x1BU; -const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; -const unsigned char MMDVM_DMR_START = 0x1DU; -const unsigned char MMDVM_DMR_ABORT = 0x1EU; - -const unsigned char MMDVM_YSF_DATA = 0x20U; -const unsigned char MMDVM_YSF_LOST = 0x21U; - -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_M17_HEADER = 0x45U; -const unsigned char MMDVM_M17_DATA = 0x46U; -const unsigned char MMDVM_M17_LOST = 0x47U; - -const unsigned char MMDVM_POCSAG_DATA = 0x50U; - -const unsigned char MMDVM_AX25_DATA = 0x55U; - -const unsigned char MMDVM_FM_PARAMS1 = 0x60U; -const unsigned char MMDVM_FM_PARAMS2 = 0x61U; -const unsigned char MMDVM_FM_PARAMS3 = 0x62U; -const unsigned char MMDVM_FM_PARAMS4 = 0x63U; -const unsigned char MMDVM_FM_DATA = 0x65U; -const unsigned char MMDVM_FM_CONTROL = 0x66U; -const unsigned char MMDVM_FM_EOT = 0x67U; - -const unsigned char MMDVM_ACK = 0x70U; -const unsigned char MMDVM_NAK = 0x7FU; - -const unsigned char MMDVM_SERIAL_DATA = 0x80U; - -const unsigned char MMDVM_TRANSPARENT = 0x90U; -const unsigned char MMDVM_QSO_INFO = 0x91U; - -const unsigned char MMDVM_DEBUG1 = 0xF1U; -const unsigned char MMDVM_DEBUG2 = 0xF2U; -const unsigned char MMDVM_DEBUG3 = 0xF3U; -const unsigned char MMDVM_DEBUG4 = 0xF4U; -const unsigned char MMDVM_DEBUG5 = 0xF5U; - -const unsigned int MAX_RESPONSES = 30U; - -const unsigned int BUFFER_LENGTH = 2000U; - - -CSerialModem::CSerialModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug) : -m_port(port), -m_protocolVersion(0U), -m_dmrColorCode(0U), -m_ysfLoDev(false), -m_ysfTXHang(4U), -m_p25TXHang(5U), -m_nxdnTXHang(5U), -m_m17TXHang(5U), -m_duplex(duplex), -m_rxInvert(rxInvert), -m_txInvert(txInvert), -m_pttInvert(pttInvert), -m_txDelay(txDelay), -m_dmrDelay(dmrDelay), -m_rxLevel(0.0F), -m_cwIdTXLevel(0.0F), -m_dstarTXLevel(0.0F), -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_ax25TXLevel(0.0F), -m_rfLevel(0.0F), -m_useCOSAsLockout(useCOSAsLockout), -m_trace(trace), -m_debug(debug), -m_rxFrequency(0U), -m_txFrequency(0U), -m_pocsagFrequency(0U), -m_dstarEnabled(false), -m_dmrEnabled(false), -m_ysfEnabled(false), -m_p25Enabled(false), -m_nxdnEnabled(false), -m_m17Enabled(false), -m_pocsagEnabled(false), -m_fmEnabled(false), -m_ax25Enabled(false), -m_rxDCOffset(0), -m_txDCOffset(0), -m_serial(NULL), -m_buffer(NULL), -m_length(0U), -m_offset(0U), -m_state(SS_START), -m_type(0U), -m_rxDStarData(1000U, "Modem RX D-Star"), -m_txDStarData(1000U, "Modem TX D-Star"), -m_rxDMRData1(1000U, "Modem RX DMR1"), -m_rxDMRData2(1000U, "Modem RX DMR2"), -m_txDMRData1(1000U, "Modem TX DMR1"), -m_txDMRData2(1000U, "Modem TX DMR2"), -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_rxM17Data(1000U, "Modem RX M17"), -m_txM17Data(1000U, "Modem TX M17"), -m_txPOCSAGData(1000U, "Modem TX POCSAG"), -m_rxFMData(5000U, "Modem RX FM"), -m_txFMData(5000U, "Modem TX FM"), -m_rxAX25Data(1000U, "Modem RX AX.25"), -m_txAX25Data(1000U, "Modem TX AX.25"), -m_rxSerialData(1000U, "Modem RX Serial"), -m_txSerialData(1000U, "Modem TX Serial"), -m_rxTransparentData(1000U, "Modem RX Transparent"), -m_txTransparentData(1000U, "Modem TX Transparent"), -m_sendTransparentDataFrameType(0U), -m_statusTimer(1000U, 0U, 250U), -m_inactivityTimer(1000U, 2U), -m_playoutTimer(1000U, 0U, 10U), -m_dstarSpace(0U), -m_dmrSpace1(0U), -m_dmrSpace2(0U), -m_ysfSpace(0U), -m_p25Space(0U), -m_nxdnSpace(0U), -m_m17Space(0U), -m_pocsagSpace(0U), -m_fmSpace(0U), -m_ax25Space(0U), -m_tx(false), -m_cd(false), -m_lockout(false), -m_error(false), -m_mode(MODE_IDLE), -m_hwType(HWT_UNKNOWN), -m_ax25RXTwist(0), -m_ax25TXDelay(300U), -m_ax25SlotTime(30U), -m_ax25PPersist(128U), -m_fmCallsign(), -m_fmCallsignSpeed(20U), -m_fmCallsignFrequency(1000U), -m_fmCallsignTime(600U), -m_fmCallsignHoldoff(0U), -m_fmCallsignHighLevel(35.0F), -m_fmCallsignLowLevel(15.0F), -m_fmCallsignAtStart(true), -m_fmCallsignAtEnd(true), -m_fmCallsignAtLatch(true), -m_fmRfAck("K"), -m_fmExtAck("N"), -m_fmAckSpeed(20U), -m_fmAckFrequency(1750U), -m_fmAckMinTime(4U), -m_fmAckDelay(1000U), -m_fmAckLevel(80.0F), -m_fmTimeout(120U), -m_fmTimeoutLevel(80.0F), -m_fmCtcssFrequency(88.4F), -m_fmCtcssHighThreshold(30U), -m_fmCtcssLowThreshold(20U), -m_fmCtcssLevel(10.0F), -m_fmKerchunkTime(0U), -m_fmHangTime(5U), -m_fmAccessMode(1U), -m_fmCOSInvert(false), -m_fmNoiseSquelch(false), -m_fmSquelchHighThreshold(30U), -m_fmSquelchLowThreshold(20U), -m_fmRFAudioBoost(1U), -m_fmExtAudioBoost(1U), -m_fmMaxDevLevel(90.0F), -m_fmExtEnable(false) -{ - m_buffer = new unsigned char[BUFFER_LENGTH]; - - assert(!port.empty()); -} - -CSerialModem::~CSerialModem() -{ - delete m_serial; - delete[] m_buffer; -} - -void CSerialModem::setSerialParams(const std::string& protocol, unsigned int address, unsigned int speed) -{ - // Create the serial controller instance according the protocol specified in conf. -#if defined(__linux__) - if (protocol == "i2c") - m_serial = new CI2CController(m_port, address); - else -#endif - m_serial = new CSerialController(m_port, speed, true); -} - -void CSerialModem::setRFParams(unsigned int rxFrequency, int rxOffset, unsigned int txFrequency, int txOffset, int txDCOffset, int rxDCOffset, float rfLevel, unsigned int pocsagFrequency) -{ - m_rxFrequency = rxFrequency + rxOffset; - m_txFrequency = txFrequency + txOffset; - m_txDCOffset = txDCOffset; - m_rxDCOffset = rxDCOffset; - m_rfLevel = rfLevel; - m_pocsagFrequency = pocsagFrequency + txOffset; -} - -void CSerialModem::setModeParams(bool dstarEnabled, bool dmrEnabled, bool ysfEnabled, bool p25Enabled, bool nxdnEnabled, bool m17Enabled, bool pocsagEnabled, bool fmEnabled, bool ax25Enabled) -{ - m_dstarEnabled = dstarEnabled; - m_dmrEnabled = dmrEnabled; - m_ysfEnabled = ysfEnabled; - m_p25Enabled = p25Enabled; - m_nxdnEnabled = nxdnEnabled; - m_m17Enabled = m17Enabled; - m_pocsagEnabled = pocsagEnabled; - m_fmEnabled = fmEnabled; - m_ax25Enabled = ax25Enabled; -} - -void CSerialModem::setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagTXLevel, float fmTXLevel, float ax25TXLevel) -{ - m_rxLevel = rxLevel; - m_cwIdTXLevel = cwIdTXLevel; - m_dstarTXLevel = dstarTXLevel; - m_dmrTXLevel = dmrTXLevel; - m_ysfTXLevel = ysfTXLevel; - m_p25TXLevel = p25TXLevel; - m_nxdnTXLevel = nxdnTXLevel; - m_m17TXLevel = m17TXLevel; - m_pocsagTXLevel = pocsagTXLevel; - m_fmTXLevel = fmTXLevel; - m_ax25TXLevel = ax25TXLevel; -} - -void CSerialModem::setDMRParams(unsigned int colorCode) -{ - assert(colorCode < 16U); - - m_dmrColorCode = colorCode; -} - -void CSerialModem::setYSFParams(bool loDev, unsigned int txHang) -{ - m_ysfLoDev = loDev; - m_ysfTXHang = txHang; -} - -void CSerialModem::setP25Params(unsigned int txHang) -{ - m_p25TXHang = txHang; -} - -void CSerialModem::setNXDNParams(unsigned int txHang) -{ - m_nxdnTXHang = txHang; -} - -void CSerialModem::setM17Params(unsigned int txHang) -{ - m_m17TXHang = txHang; -} - -void CSerialModem::setAX25Params(int rxTwist, unsigned int txDelay, unsigned int slotTime, unsigned int pPersist) -{ - m_ax25RXTwist = rxTwist; - m_ax25TXDelay = txDelay; - m_ax25SlotTime = slotTime; - m_ax25PPersist = pPersist; -} - -void CSerialModem::setTransparentDataParams(unsigned int sendFrameType) -{ - m_sendTransparentDataFrameType = sendFrameType; -} - -bool CSerialModem::open() -{ - ::LogMessage("Opening the MMDVM"); - - bool ret = m_serial->open(); - if (!ret) - return false; - - ret = readVersion(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } else { - /* Stopping the inactivity timer here when a firmware version has been - successfuly read prevents the death spiral of "no reply from modem..." */ - m_inactivityTimer.stop(); - } - - ret = setFrequency(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - - ret = writeConfig(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - - if (m_fmEnabled && m_duplex) { - ret = setFMCallsignParams(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - - ret = setFMAckParams(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - - ret = setFMMiscParams(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - - if (m_fmExtEnable) { - ret = setFMExtParams(); - if (!ret) { - m_serial->close(); - delete m_serial; - m_serial = NULL; - return false; - } - } - } - - m_statusTimer.start(); - - m_error = false; - m_offset = 0U; - - return true; -} - -void CSerialModem::clock(unsigned int ms) -{ - assert(m_serial != NULL); - - // Poll the modem status every 250ms - m_statusTimer.clock(ms); - if (m_statusTimer.hasExpired()) { - readStatus(); - m_statusTimer.start(); - } - - m_inactivityTimer.clock(ms); - if (m_inactivityTimer.hasExpired()) { - LogError("No reply from the modem for some time, resetting it"); - m_error = true; - close(); - - CThread::sleep(2000U); // 2s - while (!open()) - CThread::sleep(5000U); // 5s - } - - RESP_TYPE_MMDVM type = getResponse(); - - if (type == RTM_TIMEOUT) { - // Nothing to do - } else if (type == RTM_ERROR) { - // Nothing to do - } else { - // type == RTM_OK - switch (m_type) { - case MMDVM_DSTAR_HEADER: { - if (m_trace) - CUtils::dump(1U, "RX D-Star Header", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxDStarData.addData(&data, 1U); - - data = TAG_HEADER; - m_rxDStarData.addData(&data, 1U); - - m_rxDStarData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_DSTAR_DATA: { - if (m_trace) - CUtils::dump(1U, "RX D-Star Data", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxDStarData.addData(&data, 1U); - - data = TAG_DATA; - m_rxDStarData.addData(&data, 1U); - - m_rxDStarData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_DSTAR_LOST: { - if (m_trace) - CUtils::dump(1U, "RX D-Star Lost", m_buffer, m_length); - - unsigned char data = 1U; - m_rxDStarData.addData(&data, 1U); - - data = TAG_LOST; - m_rxDStarData.addData(&data, 1U); - } - break; - - case MMDVM_DSTAR_EOT: { - if (m_trace) - CUtils::dump(1U, "RX D-Star EOT", m_buffer, m_length); - - unsigned char data = 1U; - m_rxDStarData.addData(&data, 1U); - - data = TAG_EOT; - m_rxDStarData.addData(&data, 1U); - } - break; - - case MMDVM_DMR_DATA1: { - if (m_trace) - CUtils::dump(1U, "RX DMR Data 1", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxDMRData1.addData(&data, 1U); - - if (m_buffer[3U] == (DMR_SYNC_DATA | DT_TERMINATOR_WITH_LC)) - data = TAG_EOT; - else - data = TAG_DATA; - m_rxDMRData1.addData(&data, 1U); - - m_rxDMRData1.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_DMR_DATA2: { - if (m_trace) - CUtils::dump(1U, "RX DMR Data 2", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxDMRData2.addData(&data, 1U); - - if (m_buffer[3U] == (DMR_SYNC_DATA | DT_TERMINATOR_WITH_LC)) - data = TAG_EOT; - else - data = TAG_DATA; - m_rxDMRData2.addData(&data, 1U); - - m_rxDMRData2.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_DMR_LOST1: { - if (m_trace) - CUtils::dump(1U, "RX DMR Lost 1", m_buffer, m_length); - - unsigned char data = 1U; - m_rxDMRData1.addData(&data, 1U); - - data = TAG_LOST; - m_rxDMRData1.addData(&data, 1U); - } - break; - - case MMDVM_DMR_LOST2: { - if (m_trace) - CUtils::dump(1U, "RX DMR Lost 2", m_buffer, m_length); - - unsigned char data = 1U; - m_rxDMRData2.addData(&data, 1U); - - data = TAG_LOST; - m_rxDMRData2.addData(&data, 1U); - } - break; - - case MMDVM_YSF_DATA: { - if (m_trace) - CUtils::dump(1U, "RX YSF Data", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxYSFData.addData(&data, 1U); - - data = TAG_DATA; - m_rxYSFData.addData(&data, 1U); - - m_rxYSFData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_YSF_LOST: { - if (m_trace) - CUtils::dump(1U, "RX YSF Lost", m_buffer, m_length); - - unsigned char data = 1U; - m_rxYSFData.addData(&data, 1U); - - data = TAG_LOST; - m_rxYSFData.addData(&data, 1U); - } - break; - - case MMDVM_P25_HDR: { - if (m_trace) - CUtils::dump(1U, "RX P25 Header", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxP25Data.addData(&data, 1U); - - data = TAG_HEADER; - m_rxP25Data.addData(&data, 1U); - - m_rxP25Data.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_P25_LDU: { - if (m_trace) - CUtils::dump(1U, "RX P25 LDU", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxP25Data.addData(&data, 1U); - - data = TAG_DATA; - m_rxP25Data.addData(&data, 1U); - - m_rxP25Data.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_P25_LOST: { - if (m_trace) - CUtils::dump(1U, "RX P25 Lost", m_buffer, m_length); - - unsigned char data = 1U; - m_rxP25Data.addData(&data, 1U); - - data = TAG_LOST; - m_rxP25Data.addData(&data, 1U); - } - break; - - case MMDVM_NXDN_DATA: { - if (m_trace) - CUtils::dump(1U, "RX NXDN Data", m_buffer, m_length); - - unsigned char data = m_length - m_offset + 1U; - m_rxNXDNData.addData(&data, 1U); - - data = TAG_DATA; - m_rxNXDNData.addData(&data, 1U); - - m_rxNXDNData.addData(m_buffer + m_offset, m_length - m_offset); - } - 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_M17_HEADER: { - if (m_trace) - CUtils::dump(1U, "RX M17 Header", m_buffer, m_length); - - unsigned char data = m_length - 2U; - m_rxM17Data.addData(&data, 1U); - - data = TAG_HEADER; - m_rxM17Data.addData(&data, 1U); - - m_rxM17Data.addData(m_buffer + 3U, m_length - 3U); - } - 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_FM_DATA: { - if (m_trace) - CUtils::dump(1U, "RX FM Data", m_buffer, m_length); - - unsigned int data1 = m_length - m_offset + 1U; - m_rxFMData.addData((unsigned char*)&data1, sizeof(unsigned int)); - - unsigned char data2 = TAG_DATA; - m_rxFMData.addData(&data2, 1U); - - m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_FM_CONTROL: { - if (m_trace) - CUtils::dump(1U, "RX FM Control", m_buffer, m_length); - - unsigned int data1 = m_length - m_offset + 1U; - m_rxFMData.addData((unsigned char*)&data1, sizeof(unsigned int)); - - unsigned char data2= TAG_HEADER; - m_rxFMData.addData(&data2, 1U); - - m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_FM_EOT: { - if(m_trace) - CUtils::dump(1U, "RX FM End of transmission", m_buffer, m_length); - - unsigned int data1 = m_length - m_offset + 1U; - m_rxFMData.addData((unsigned char*)&data1, sizeof(unsigned int)); - - unsigned char data2 = TAG_EOT; - m_rxFMData.addData(&data2, 1U); - - m_rxFMData.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_AX25_DATA: { - if (m_trace) - CUtils::dump(1U, "RX AX.25 Data", m_buffer, m_length); - - unsigned int data = m_length - m_offset; - m_rxAX25Data.addData((unsigned char*)&data, sizeof(unsigned int)); - - m_rxAX25Data.addData(m_buffer + m_offset, m_length - m_offset); - } - break; - - case MMDVM_GET_STATUS: - // if (m_trace) - // CUtils::dump(1U, "GET_STATUS", m_buffer, m_length); - - switch (m_protocolVersion) { - case 1U: { - m_mode = m_buffer[m_offset + 1U]; - - m_tx = (m_buffer[m_offset + 2U] & 0x01U) == 0x01U; - bool adcOverflow = (m_buffer[m_offset + 2U] & 0x02U) == 0x02U; - if (adcOverflow) - LogError("MMDVM ADC levels have overflowed"); - bool rxOverflow = (m_buffer[m_offset + 2U] & 0x04U) == 0x04U; - if (rxOverflow) - LogError("MMDVM RX buffer has overflowed"); - bool txOverflow = (m_buffer[m_offset + 2U] & 0x08U) == 0x08U; - if (txOverflow) - LogError("MMDVM TX buffer has overflowed"); - m_lockout = (m_buffer[m_offset + 2U] & 0x10U) == 0x10U; - bool dacOverflow = (m_buffer[m_offset + 2U] & 0x20U) == 0x20U; - if (dacOverflow) - LogError("MMDVM DAC levels have overflowed"); - m_cd = (m_buffer[m_offset + 2U] & 0x40U) == 0x40U; - - m_p25Space = 0U; - m_nxdnSpace = 0U; - m_m17Space = 0U; - m_pocsagSpace = 0U; - m_fmSpace = 0U; - m_ax25Space = 0U; - - m_dstarSpace = m_buffer[m_offset + 3U]; - m_dmrSpace1 = m_buffer[m_offset + 4U]; - m_dmrSpace2 = m_buffer[m_offset + 5U]; - m_ysfSpace = m_buffer[m_offset + 6U]; - - // The following depend on the version of the firmware - if (m_length > (m_offset + 7U)) - m_p25Space = m_buffer[m_offset + 7U]; - if (m_length > (m_offset + 8U)) - m_nxdnSpace = m_buffer[m_offset + 8U]; - if (m_length > (m_offset + 9U)) - m_pocsagSpace = m_buffer[m_offset + 9U]; - if (m_length > (m_offset + 10U)) - m_m17Space = m_buffer[m_offset + 10U]; - } - break; - - case 2U: { - m_mode = m_buffer[m_offset + 0U]; - - m_tx = (m_buffer[m_offset + 1U] & 0x01U) == 0x01U; - bool adcOverflow = (m_buffer[m_offset + 1U] & 0x02U) == 0x02U; - if (adcOverflow) - LogError("MMDVM ADC levels have overflowed"); - bool rxOverflow = (m_buffer[m_offset + 1U] & 0x04U) == 0x04U; - if (rxOverflow) - LogError("MMDVM RX buffer has overflowed"); - bool txOverflow = (m_buffer[m_offset + 1U] & 0x08U) == 0x08U; - if (txOverflow) - LogError("MMDVM TX buffer has overflowed"); - m_lockout = (m_buffer[m_offset + 1U] & 0x10U) == 0x10U; - bool dacOverflow = (m_buffer[m_offset + 1U] & 0x20U) == 0x20U; - if (dacOverflow) - LogError("MMDVM DAC levels have overflowed"); - m_cd = (m_buffer[m_offset + 1U] & 0x40U) == 0x40U; - - m_dstarSpace = m_buffer[m_offset + 3U]; - m_dmrSpace1 = m_buffer[m_offset + 4U]; - m_dmrSpace2 = m_buffer[m_offset + 5U]; - m_ysfSpace = m_buffer[m_offset + 6U]; - m_p25Space = m_buffer[m_offset + 7U]; - m_nxdnSpace = m_buffer[m_offset + 8U]; - m_m17Space = m_buffer[m_offset + 9U]; - m_fmSpace = m_buffer[m_offset + 10U]; - m_pocsagSpace = m_buffer[m_offset + 11U]; - m_ax25Space = m_buffer[m_offset + 12U]; - } - break; - - default: - m_dstarSpace = 0U; - m_dmrSpace1 = 0U; - m_dmrSpace2 = 0U; - m_ysfSpace = 0U; - m_p25Space = 0U; - m_nxdnSpace = 0U; - m_m17Space = 0U; - m_pocsagSpace = 0U; - m_fmSpace = 0U; - m_ax25Space = 0U; - break; - } - - m_inactivityTimer.start(); - // LogMessage("status=%02X, tx=%d, space=%u,%u,%u,%u,%u,%u,%u,%u,%u,%u lockout=%d, cd=%d", m_buffer[m_offset + 2U], int(m_tx), m_dstarSpace, m_dmrSpace1, m_dmrSpace2, m_ysfSpace, m_p25Space, m_nxdnSpace, m_m17Space, m_pocsagSpace, m_fmSpace, m_ax25Space, int(m_lockout), int(m_cd)); - break; - - case MMDVM_TRANSPARENT: { - if (m_trace) - CUtils::dump(1U, "RX Transparent Data", m_buffer, m_length); - - unsigned char offset = m_sendTransparentDataFrameType; - if (offset > 1U) offset = 1U; - unsigned char data = m_length - m_offset + offset; - m_rxTransparentData.addData(&data, 1U); - - m_rxTransparentData.addData(m_buffer + m_offset - offset, m_length - m_offset + offset); - } - break; - - // These should not be received, but don't complain if we do - case MMDVM_GET_VERSION: - case MMDVM_ACK: - break; - - case MMDVM_NAK: - LogWarning("Received a NAK from the MMDVM, command = 0x%02X, reason = %u", m_buffer[m_offset], m_buffer[m_offset + 1U]); - break; - - case MMDVM_DEBUG1: - case MMDVM_DEBUG2: - case MMDVM_DEBUG3: - case MMDVM_DEBUG4: - case MMDVM_DEBUG5: - printDebug(); - break; - - case MMDVM_SERIAL_DATA: - if (m_trace) - CUtils::dump(1U, "RX Serial Data", m_buffer, m_length); - m_rxSerialData.addData(m_buffer + m_offset, m_length - m_offset); - break; - - default: - LogMessage("Unknown message, type: %02X", m_type); - CUtils::dump("Buffer dump", m_buffer, m_length); - break; - } - } - - // Only feed data to the modem if the playout timer has expired - m_playoutTimer.clock(ms); - if (!m_playoutTimer.hasExpired()) - return; - - if (m_dstarSpace > 1U && !m_txDStarData.isEmpty()) { - unsigned char buffer[4U]; - m_txDStarData.peek(buffer, 4U); - - if ((buffer[3U] == MMDVM_DSTAR_HEADER && m_dstarSpace > 4U) || - (buffer[3U] == MMDVM_DSTAR_DATA && m_dstarSpace > 1U) || - (buffer[3U] == MMDVM_DSTAR_EOT && m_dstarSpace > 1U)) { - unsigned char len = 0U; - m_txDStarData.getData(&len, 1U); - m_txDStarData.getData(m_buffer, len); - - switch (buffer[3U]) { - case MMDVM_DSTAR_HEADER: - if (m_trace) - CUtils::dump(1U, "TX D-Star Header", m_buffer, len); - m_dstarSpace -= 4U; - break; - case MMDVM_DSTAR_DATA: - if (m_trace) - CUtils::dump(1U, "TX D-Star Data", m_buffer, len); - m_dstarSpace -= 1U; - break; - default: - if (m_trace) - CUtils::dump(1U, "TX D-Star EOT", m_buffer, len); - m_dstarSpace -= 1U; - break; - } - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing D-Star data to the MMDVM"); - - m_playoutTimer.start(); - } - } - - if (m_dmrSpace1 > 1U && !m_txDMRData1.isEmpty()) { - unsigned char len = 0U; - m_txDMRData1.getData(&len, 1U); - m_txDMRData1.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX DMR Data 1", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing DMR data to the MMDVM"); - - m_playoutTimer.start(); - - m_dmrSpace1--; - } - - if (m_dmrSpace2 > 1U && !m_txDMRData2.isEmpty()) { - unsigned char len = 0U; - m_txDMRData2.getData(&len, 1U); - m_txDMRData2.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX DMR Data 2", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing DMR data to the MMDVM"); - - m_playoutTimer.start(); - - m_dmrSpace2--; - } - - if (m_ysfSpace > 1U && !m_txYSFData.isEmpty()) { - unsigned char len = 0U; - m_txYSFData.getData(&len, 1U); - m_txYSFData.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX YSF Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing YSF data to the MMDVM"); - - m_playoutTimer.start(); - - m_ysfSpace--; - } - - if (m_p25Space > 1U && !m_txP25Data.isEmpty()) { - unsigned char len = 0U; - m_txP25Data.getData(&len, 1U); - m_txP25Data.getData(m_buffer, len); - - if (m_trace) { - if (m_buffer[2U] == MMDVM_P25_HDR) - CUtils::dump(1U, "TX P25 HDR", m_buffer, len); - else - CUtils::dump(1U, "TX P25 LDU", m_buffer, len); - } - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing P25 data to the MMDVM"); - - m_playoutTimer.start(); - - 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--; - } - - 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) { - if (m_buffer[2U] == MMDVM_M17_HEADER) - CUtils::dump(1U, "TX M17 Header", m_buffer, len); - else - 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); - m_txPOCSAGData.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX POCSAG Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing POCSAG data to the MMDVM"); - - m_playoutTimer.start(); - - m_pocsagSpace--; - } - - if (m_fmSpace > 1U && !m_txFMData.isEmpty()) { - unsigned int len = 0U; - m_txFMData.getData((unsigned char*)&len, sizeof(unsigned int)); - m_txFMData.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX FM Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing FM data to the MMDVM"); - - m_playoutTimer.start(); - - m_fmSpace--; - } - - if (m_ax25Space > 0U && !m_txAX25Data.isEmpty()) { - unsigned int len = 0U; - m_txAX25Data.getData((unsigned char*)&len, sizeof(unsigned int)); - m_txAX25Data.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX AX.25 Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing AX.25 data to the MMDVM"); - - m_playoutTimer.start(); - - m_ax25Space = 0U; - } - - if (!m_txTransparentData.isEmpty()) { - unsigned char len = 0U; - m_txTransparentData.getData(&len, 1U); - m_txTransparentData.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX Transparent Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing Transparent data to the MMDVM"); - } - - if (!m_txSerialData.isEmpty()) { - unsigned char len = 0U; - m_txSerialData.getData(&len, 1U); - m_txSerialData.getData(m_buffer, len); - - if (m_trace) - CUtils::dump(1U, "TX Serial Data", m_buffer, len); - - int ret = m_serial->write(m_buffer, len); - if (ret != int(len)) - LogWarning("Error when writing Serial data to the MMDVM"); - } -} - -void CSerialModem::close() -{ - assert(m_serial != NULL); - - ::LogMessage("Closing the MMDVM"); - - m_serial->close(); -} - -unsigned int CSerialModem::readDStarData(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxDStarData.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxDStarData.getData(&len, 1U); - m_rxDStarData.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readDMRData1(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxDMRData1.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxDMRData1.getData(&len, 1U); - m_rxDMRData1.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readDMRData2(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxDMRData2.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxDMRData2.getData(&len, 1U); - m_rxDMRData2.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readYSFData(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxYSFData.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxYSFData.getData(&len, 1U); - m_rxYSFData.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readP25Data(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxP25Data.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxP25Data.getData(&len, 1U); - m_rxP25Data.getData(data, len); - - return len; -} - -unsigned int CSerialModem::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; -} - -unsigned int CSerialModem::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 CSerialModem::readFMData(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxFMData.isEmpty()) - return 0U; - - unsigned int len = 0U; - m_rxFMData.getData((unsigned char*)&len, sizeof(unsigned int)); - m_rxFMData.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readAX25Data(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxAX25Data.isEmpty()) - return 0U; - - unsigned int len = 0U; - m_rxAX25Data.getData((unsigned char*)&len, sizeof(unsigned int)); - m_rxAX25Data.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readTransparentData(unsigned char* data) -{ - assert(data != NULL); - - if (m_rxTransparentData.isEmpty()) - return 0U; - - unsigned char len = 0U; - m_rxTransparentData.getData(&len, 1U); - m_rxTransparentData.getData(data, len); - - return len; -} - -unsigned int CSerialModem::readSerial(unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned int n = 0U; - while (!m_rxSerialData.isEmpty() && n < length) { - m_rxSerialData.getData(data + n, 1U); - n++; - } - - return n; -} - -bool CSerialModem::hasDStarSpace() const -{ - unsigned int space = m_txDStarData.freeSpace() / (DSTAR_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writeDStarData(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[50U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 2U; - - switch (data[0U]) { - case TAG_HEADER: - buffer[2U] = MMDVM_DSTAR_HEADER; - break; - case TAG_DATA: - buffer[2U] = MMDVM_DSTAR_DATA; - break; - case TAG_EOT: - buffer[2U] = MMDVM_DSTAR_EOT; - break; - default: - CUtils::dump(2U, "Unknown D-Star packet type", data, length); - return false; - } - - ::memcpy(buffer + 3U, data + 1U, length - 1U); - - unsigned char len = length + 2U; - m_txDStarData.addData(&len, 1U); - m_txDStarData.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasDMRSpace1() const -{ - unsigned int space = m_txDMRData1.freeSpace() / (DMR_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::hasDMRSpace2() const -{ - unsigned int space = m_txDMRData2.freeSpace() / (DMR_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writeDMRData1(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[40U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 2U; - buffer[2U] = MMDVM_DMR_DATA1; - - ::memcpy(buffer + 3U, data + 1U, length - 1U); - - unsigned char len = length + 2U; - m_txDMRData1.addData(&len, 1U); - m_txDMRData1.addData(buffer, len); - - return true; -} - -bool CSerialModem::writeDMRData2(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[40U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 2U; - buffer[2U] = MMDVM_DMR_DATA2; - - ::memcpy(buffer + 3U, data + 1U, length - 1U); - - unsigned char len = length + 2U; - m_txDMRData2.addData(&len, 1U); - m_txDMRData2.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasYSFSpace() const -{ - unsigned int space = m_txYSFData.freeSpace() / (YSF_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writeYSFData(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_YSF_DATA; - - ::memcpy(buffer + 3U, data + 1U, length - 1U); - - unsigned char len = length + 2U; - m_txYSFData.addData(&len, 1U); - m_txYSFData.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasP25Space() const -{ - unsigned int space = m_txP25Data.freeSpace() / (P25_LDU_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writeP25Data(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - if (data[0U] != TAG_HEADER && data[0U] != TAG_DATA && data[0U] != TAG_EOT) - return false; - - unsigned char buffer[250U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 2U; - buffer[2U] = (data[0U] == TAG_HEADER) ? MMDVM_P25_HDR : MMDVM_P25_LDU; - - ::memcpy(buffer + 3U, data + 1U, length - 1U); - - unsigned char len = length + 2U; - m_txP25Data.addData(&len, 1U); - m_txP25Data.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasNXDNSpace() const -{ - unsigned int space = m_txNXDNData.freeSpace() / (NXDN_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::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 CSerialModem::hasM17Space() const -{ - unsigned int space = m_txM17Data.freeSpace() / (M17_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writeM17Data(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - if (data[0U] != TAG_HEADER && data[0U] != TAG_DATA && data[0U] != TAG_EOT) - return false; - - unsigned char buffer[130U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 2U; - - if (data[0U] == TAG_HEADER) - buffer[2U] = MMDVM_M17_HEADER; - else - 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 CSerialModem::hasPOCSAGSpace() const -{ - unsigned int space = m_txPOCSAGData.freeSpace() / (POCSAG_FRAME_LENGTH_BYTES + 4U); - - return space > 1U; -} - -bool CSerialModem::writePOCSAGData(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[130U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_POCSAG_DATA; - - ::memcpy(buffer + 3U, data, length); - - unsigned char len = length + 3U; - m_txPOCSAGData.addData(&len, 1U); - m_txPOCSAGData.addData(buffer, len); - - return true; -} - -unsigned int CSerialModem::getFMSpace() const -{ - return m_txFMData.freeSpace(); -} - -bool CSerialModem::writeFMData(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[500U]; - - unsigned int len; - if (length > 252U) { - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 0U; - buffer[2U] = (length + 4U) - 255U; - buffer[3U] = MMDVM_FM_DATA; - ::memcpy(buffer + 4U, data, length); - len = length + 4U; - } else { - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_FM_DATA; - ::memcpy(buffer + 3U, data, length); - len = length + 3U; - } - - m_txFMData.addData((unsigned char*)&len, sizeof(unsigned int)); - m_txFMData.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasAX25Space() const -{ - unsigned int space = m_txAX25Data.freeSpace() / (AX25_MAX_FRAME_LENGTH_BYTES + 5U); - - return space > 1U; -} - -bool CSerialModem::writeAX25Data(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[500U]; - - unsigned int len; - if (length > 252U) { - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 0U; - buffer[2U] = (length + 4U) - 255U; - buffer[3U] = MMDVM_AX25_DATA; - ::memcpy(buffer + 4U, data, length); - len = length + 4U; - } else { - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_AX25_DATA; - ::memcpy(buffer + 3U, data, length); - len = length + 3U; - } - - m_txAX25Data.addData((unsigned char*)&len, sizeof(unsigned int)); - m_txAX25Data.addData(buffer, len); - - return true; -} - -bool CSerialModem::writeTransparentData(const unsigned char* data, unsigned int length) -{ - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[250U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_TRANSPARENT; - - if (m_sendTransparentDataFrameType > 0U) { - ::memcpy(buffer + 2U, data, length); - length--; - buffer[1U]--; - - //when sendFrameType==1 , only 0x80 and 0x90 (MMDVM_SERIAL and MMDVM_TRANSPARENT) are allowed - // and reverted to default (MMDVM_TRANSPARENT) for any other value - //when >1, frame type is not checked - if (m_sendTransparentDataFrameType == 1U) { - if ((buffer[2U] & 0xE0) != 0x80) - buffer[2U] = MMDVM_TRANSPARENT; - } - } else { - ::memcpy(buffer + 3U, data, length); - } - - unsigned char len = length + 3U; - m_txTransparentData.addData(&len, 1U); - m_txTransparentData.addData(buffer, len); - - return true; -} - -bool CSerialModem::writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) -{ - assert(m_serial != NULL); - assert(my1 != NULL); - assert(my2 != NULL); - assert(your != NULL); - assert(type != NULL); - assert(reflector != NULL); - - unsigned char buffer[50U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 33U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_DSTAR; - - ::memcpy(buffer + 4U, my1, DSTAR_LONG_CALLSIGN_LENGTH); - ::memcpy(buffer + 12U, my2, DSTAR_SHORT_CALLSIGN_LENGTH); - - ::memcpy(buffer + 16U, your, DSTAR_LONG_CALLSIGN_LENGTH); - - ::memcpy(buffer + 24U, type, 1U); - - ::memcpy(buffer + 25U, reflector, DSTAR_LONG_CALLSIGN_LENGTH); - - return m_serial->write(buffer, 33U) != 33; -} - -bool CSerialModem::writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dest, const char* type) -{ - assert(m_serial != NULL); - assert(type != NULL); - - unsigned char buffer[50U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 47U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_DMR; - - buffer[4U] = slotNo; - - ::sprintf((char*)(buffer + 5U), "%20.20s", src.c_str()); - - buffer[25U] = group ? 'G' : 'I'; - - ::sprintf((char*)(buffer + 26U), "%20.20s", dest.c_str()); - - ::memcpy(buffer + 46U, type, 1U); - - return m_serial->write(buffer, 47U) != 47; -} - -bool CSerialModem::writeYSFInfo(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin) -{ - assert(m_serial != NULL); - assert(source != NULL); - assert(dest != NULL); - assert(type != NULL); - assert(origin != NULL); - - unsigned char buffer[40U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 36U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_YSF; - - ::memcpy(buffer + 4U, source, YSF_CALLSIGN_LENGTH); - ::memcpy(buffer + 14U, dest, YSF_CALLSIGN_LENGTH); - - ::memcpy(buffer + 24U, type, 1U); - - ::memcpy(buffer + 25U, origin, YSF_CALLSIGN_LENGTH); - - buffer[35U] = dgid; - - return m_serial->write(buffer, 36U) != 36; -} - -bool CSerialModem::writeP25Info(const char* source, bool group, unsigned int dest, const char* type) -{ - assert(m_serial != NULL); - assert(source != NULL); - assert(type != NULL); - - unsigned char buffer[40U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 31U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_DMR; - - ::sprintf((char*)(buffer + 4U), "%20.20s", source); - - buffer[24U] = group ? 'G' : 'I'; - - ::sprintf((char*)(buffer + 25U), "%05u", dest); // 16-bits - - ::memcpy(buffer + 30U, type, 1U); - - return m_serial->write(buffer, 31U) != 31; -} - -bool CSerialModem::writeNXDNInfo(const char* source, bool group, unsigned int dest, const char* type) -{ - assert(m_serial != NULL); - assert(source != NULL); - assert(type != NULL); - - unsigned char buffer[40U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 31U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_NXDN; - - ::sprintf((char*)(buffer + 4U), "%20.20s", source); - - buffer[24U] = group ? 'G' : 'I'; - - ::sprintf((char*)(buffer + 25U), "%05u", dest); // 16-bits - - ::memcpy(buffer + 30U, type, 1U); - - return m_serial->write(buffer, 31U) != 31; -} - -bool CSerialModem::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 CSerialModem::writePOCSAGInfo(unsigned int ric, const std::string& message) -{ - assert(m_serial != NULL); - - size_t length = message.size(); - - unsigned char buffer[250U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 11U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = MODE_POCSAG; - - ::sprintf((char*)(buffer + 4U), "%07u", ric); // 21-bits - - ::memcpy(buffer + 11U, message.c_str(), length); - - int ret = m_serial->write(buffer, length + 11U); - - return ret != int(length + 11U); -} - -bool CSerialModem::writeIPInfo(const std::string& address) -{ - assert(m_serial != NULL); - - size_t length = address.size(); - - unsigned char buffer[25U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 4U; - buffer[2U] = MMDVM_QSO_INFO; - - buffer[3U] = 250U; - - ::memcpy(buffer + 4U, address.c_str(), length); - - int ret = m_serial->write(buffer, length + 4U); - - return ret != int(length + 4U); -} - -bool CSerialModem::writeSerial(const unsigned char* data, unsigned int length) -{ - assert(m_serial != NULL); - assert(data != NULL); - assert(length > 0U); - - unsigned char buffer[255U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_SERIAL_DATA; - - ::memcpy(buffer + 3U, data, length); - - unsigned char len = length + 3U; - m_txSerialData.addData(&len, 1U); - m_txSerialData.addData(buffer, len); - - return true; -} - -bool CSerialModem::hasTX() const -{ - return m_tx; -} - -bool CSerialModem::hasCD() const -{ - return m_cd; -} - -bool CSerialModem::hasLockout() const -{ - return m_lockout; -} - -bool CSerialModem::hasError() const -{ - return m_error; -} - -bool CSerialModem::readVersion() -{ - assert(m_serial != NULL); - - CThread::sleep(2000U); // 2s - - for (unsigned int i = 0U; i < 6U; i++) { - unsigned char buffer[3U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 3U; - buffer[2U] = MMDVM_GET_VERSION; - - // CUtils::dump(1U, "Written", buffer, 3U); - - int ret = m_serial->write(buffer, 3U); - if (ret != 3) - return false; - -#if defined(__APPLE__) - m_serial->setNonblock(true); -#endif - - for (unsigned int count = 0U; count < MAX_RESPONSES; count++) { - CThread::sleep(10U); - RESP_TYPE_MMDVM resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] == MMDVM_GET_VERSION) { - if (::memcmp(m_buffer + 4U, "MMDVM ", 6U) == 0) - m_hwType = HWT_MMDVM; - else if (::memcmp(m_buffer + 4U, "DVMEGA", 6U) == 0) - m_hwType = HWT_DVMEGA; - else if (::memcmp(m_buffer + 4U, "ZUMspot", 7U) == 0) - m_hwType = HWT_MMDVM_ZUMSPOT; - else if (::memcmp(m_buffer + 4U, "MMDVM_HS_Hat", 12U) == 0) - m_hwType = HWT_MMDVM_HS_HAT; - else if (::memcmp(m_buffer + 4U, "MMDVM_HS_Dual_Hat", 17U) == 0) - m_hwType = HWT_MMDVM_HS_DUAL_HAT; - else if (::memcmp(m_buffer + 4U, "Nano_hotSPOT", 12U) == 0) - m_hwType = HWT_NANO_HOTSPOT; - else if (::memcmp(m_buffer + 4U, "Nano_DV", 7U) == 0) - m_hwType = HWT_NANO_DV; - else if (::memcmp(m_buffer + 4U, "D2RG_MMDVM_HS", 13U) == 0) - m_hwType = HWT_D2RG_MMDVM_HS; - else if (::memcmp(m_buffer + 4U, "MMDVM_HS-", 9U) == 0) - m_hwType = HWT_MMDVM_HS; - else if (::memcmp(m_buffer + 4U, "OpenGD77_HS", 11U) == 0) - m_hwType = HWT_OPENGD77_HS; - else if (::memcmp(m_buffer + 4U, "SkyBridge", 9U) == 0) - m_hwType = HWT_SKYBRIDGE; - - m_protocolVersion = m_buffer[3U]; - - switch (m_protocolVersion) { - case 1U: - LogInfo("MMDVM protocol version: %u, description: %.*s", m_protocolVersion, m_length - 4U, m_buffer + 4U); - return true; - - case 2U: - LogInfo("MMDVM protocol version: %u, description: %.*s", m_protocolVersion, m_length - 23U, m_buffer + 23U); - switch (m_buffer[6U]) { - case 0U: - LogInfo("CPU: Atmel ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U], m_buffer[21U], m_buffer[22U]); - break; - case 1U: - LogInfo("CPU: NXP ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U], m_buffer[21U], m_buffer[22U]); - break; - case 2U: - LogInfo("CPU: ST-Micro ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U]); - break; - default: - LogInfo("CPU: Unknown type: %u", m_buffer[6U]); - break; - } - char modeText[100U]; - ::strcpy(modeText, "Modes:"); - if ((m_buffer[4U] & 0x01U) == 0x01U) - ::strcat(modeText, " D-Star"); - if ((m_buffer[4U] & 0x02U) == 0x02U) - ::strcat(modeText, " DMR"); - if ((m_buffer[4U] & 0x04U) == 0x04U) - ::strcat(modeText, " YSF"); - if ((m_buffer[4U] & 0x08U) == 0x08U) - ::strcat(modeText, " P25"); - if ((m_buffer[4U] & 0x10U) == 0x10U) - ::strcat(modeText, " NXDN"); - if ((m_buffer[4U] & 0x20U) == 0x20U) - ::strcat(modeText, " M17"); - if ((m_buffer[4U] & 0x40U) == 0x40U) - ::strcat(modeText, " FM"); - if ((m_buffer[5U] & 0x01U) == 0x01U) - ::strcat(modeText, " POCSAG"); - if ((m_buffer[5U] & 0x02U) == 0x02U) - ::strcat(modeText, " AX.25"); - LogInfo(modeText); - return true; - - default: - LogError("MMDVM protocol version: %u, unsupported by this version of the MMDVM Host", m_protocolVersion); - return false; - } - - return true; - } - } - - CThread::sleep(1500U); - } - - LogError("Unable to read the firmware version after six attempts"); - - return false; -} - -bool CSerialModem::readStatus() -{ - assert(m_serial != NULL); - - unsigned char buffer[3U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 3U; - buffer[2U] = MMDVM_GET_STATUS; - - // CUtils::dump(1U, "Written", buffer, 3U); - - return m_serial->write(buffer, 3U) == 3; -} - -bool CSerialModem::writeConfig() -{ - switch (m_protocolVersion) { - case 1U: - return setConfig1(); - case 2U: - return setConfig2(); - default: - return false; - } -} - -bool CSerialModem::setConfig1() -{ - assert(m_serial != NULL); - - unsigned char buffer[30U]; - - buffer[0U] = MMDVM_FRAME_START; - - buffer[1U] = 26U; - - buffer[2U] = MMDVM_SET_CONFIG; - - buffer[3U] = 0x00U; - if (m_rxInvert) - buffer[3U] |= 0x01U; - if (m_txInvert) - buffer[3U] |= 0x02U; - if (m_pttInvert) - buffer[3U] |= 0x04U; - if (m_ysfLoDev) - buffer[3U] |= 0x08U; - if (m_debug) - buffer[3U] |= 0x10U; - if (m_useCOSAsLockout) - buffer[3U] |= 0x20U; - if (!m_duplex) - buffer[3U] |= 0x80U; - - buffer[4U] = 0x00U; - if (m_dstarEnabled) - buffer[4U] |= 0x01U; - if (m_dmrEnabled) - buffer[4U] |= 0x02U; - if (m_ysfEnabled) - buffer[4U] |= 0x04U; - if (m_p25Enabled) - buffer[4U] |= 0x08U; - if (m_nxdnEnabled) - buffer[4U] |= 0x10U; - if (m_pocsagEnabled) - buffer[4U] |= 0x20U; - if (m_m17Enabled) - buffer[4U] |= 0x40U; - - buffer[5U] = m_txDelay / 10U; // In 10ms units - - buffer[6U] = MODE_IDLE; - - buffer[7U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F); - - buffer[8U] = (unsigned char)(m_cwIdTXLevel * 2.55F + 0.5F); - - buffer[9U] = m_dmrColorCode; - - buffer[10U] = m_dmrDelay; - - buffer[11U] = 128U; // Was OscOffset - - buffer[12U] = (unsigned char)(m_dstarTXLevel * 2.55F + 0.5F); - buffer[13U] = (unsigned char)(m_dmrTXLevel * 2.55F + 0.5F); - buffer[14U] = (unsigned char)(m_ysfTXLevel * 2.55F + 0.5F); - buffer[15U] = (unsigned char)(m_p25TXLevel * 2.55F + 0.5F); - - buffer[16U] = (unsigned char)(m_txDCOffset + 128); - buffer[17U] = (unsigned char)(m_rxDCOffset + 128); - - buffer[18U] = (unsigned char)(m_nxdnTXLevel * 2.55F + 0.5F); - - buffer[19U] = (unsigned char)m_ysfTXHang; - - buffer[20U] = (unsigned char)(m_pocsagTXLevel * 2.55F + 0.5F); - - buffer[21U] = (unsigned char)(m_fmTXLevel * 2.55F + 0.5F); - - buffer[22U] = (unsigned char)m_p25TXHang; - - buffer[23U] = (unsigned char)m_nxdnTXHang; - - buffer[24U] = (unsigned char)(m_m17TXLevel * 2.55F + 0.5F); - - buffer[25U] = (unsigned char)m_m17TXHang; - - // CUtils::dump(1U, "Written", buffer, 26U); - - int ret = m_serial->write(buffer, 26U); - if (ret != 26) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_CONFIG command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_CONFIG command from the modem"); - return false; - } - - m_playoutTimer.start(); - - return true; -} - -bool CSerialModem::setConfig2() -{ - assert(m_serial != NULL); - - unsigned char buffer[50U]; - - buffer[0U] = MMDVM_FRAME_START; - - buffer[1U] = 40U; - - buffer[2U] = MMDVM_SET_CONFIG; - - buffer[3U] = 0x00U; - if (m_rxInvert) - buffer[3U] |= 0x01U; - if (m_txInvert) - buffer[3U] |= 0x02U; - if (m_pttInvert) - buffer[3U] |= 0x04U; - if (m_ysfLoDev) - buffer[3U] |= 0x08U; - if (m_debug) - buffer[3U] |= 0x10U; - if (m_useCOSAsLockout) - buffer[3U] |= 0x20U; - if (!m_duplex) - buffer[3U] |= 0x80U; - - buffer[4U] = 0x00U; - if (m_dstarEnabled) - buffer[4U] |= 0x01U; - if (m_dmrEnabled) - buffer[4U] |= 0x02U; - if (m_ysfEnabled) - buffer[4U] |= 0x04U; - if (m_p25Enabled) - buffer[4U] |= 0x08U; - if (m_nxdnEnabled) - buffer[4U] |= 0x10U; - if (m_fmEnabled) - buffer[4U] |= 0x20U; - if (m_m17Enabled) - buffer[4U] |= 0x40U; - - buffer[5U] = 0x00U; - if (m_pocsagEnabled) - buffer[5U] |= 0x01U; - if (m_ax25Enabled) - buffer[5U] |= 0x02U; - - buffer[6U] = m_txDelay / 10U; // In 10ms units - - buffer[7U] = MODE_IDLE; - - buffer[8U] = (unsigned char)(m_txDCOffset + 128); - buffer[9U] = (unsigned char)(m_rxDCOffset + 128); - - buffer[10U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F); - - buffer[11U] = (unsigned char)(m_cwIdTXLevel * 2.55F + 0.5F); - buffer[12U] = (unsigned char)(m_dstarTXLevel * 2.55F + 0.5F); - buffer[13U] = (unsigned char)(m_dmrTXLevel * 2.55F + 0.5F); - buffer[14U] = (unsigned char)(m_ysfTXLevel * 2.55F + 0.5F); - buffer[15U] = (unsigned char)(m_p25TXLevel * 2.55F + 0.5F); - buffer[16U] = (unsigned char)(m_nxdnTXLevel * 2.55F + 0.5F); - buffer[17U] = (unsigned char)(m_m17TXLevel * 2.55F + 0.5F); - buffer[18U] = (unsigned char)(m_pocsagTXLevel * 2.55F + 0.5F); - buffer[19U] = (unsigned char)(m_fmTXLevel * 2.55F + 0.5F); - buffer[20U] = (unsigned char)(m_ax25TXLevel * 2.55F + 0.5F); - buffer[21U] = 0x00U; - buffer[22U] = 0x00U; - - buffer[23U] = (unsigned char)m_ysfTXHang; - buffer[24U] = (unsigned char)m_p25TXHang; - buffer[25U] = (unsigned char)m_nxdnTXHang; - buffer[26U] = (unsigned char)m_m17TXHang; - buffer[27U] = 0x00U; - buffer[28U] = 0x00U; - - buffer[29U] = m_dmrColorCode; - buffer[30U] = m_dmrDelay; - - buffer[31U] = (unsigned char)(m_ax25RXTwist + 128); - buffer[32U] = m_ax25TXDelay / 10U; // In 10ms units - buffer[33U] = m_ax25SlotTime / 10U; // In 10ms units - buffer[34U] = m_ax25PPersist; - - buffer[35U] = 0x00U; - buffer[36U] = 0x00U; - buffer[37U] = 0x00U; - buffer[38U] = 0x00U; - buffer[39U] = 0x00U; - - // CUtils::dump(1U, "Written", buffer, 40U); - - int ret = m_serial->write(buffer, 40U); - if (ret != 40) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_CONFIG command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_CONFIG command from the modem"); - return false; - } - - m_playoutTimer.start(); - - return true; -} - -bool CSerialModem::setFrequency() -{ - assert(m_serial != NULL); - - unsigned char buffer[20U]; - unsigned char len; - unsigned int pocsagFrequency = 433000000U; - - if (m_pocsagEnabled) - pocsagFrequency = m_pocsagFrequency; - - if (m_hwType == HWT_DVMEGA) - len = 12U; - else { - buffer[12U] = (unsigned char)(m_rfLevel * 2.55F + 0.5F); - - buffer[13U] = (pocsagFrequency >> 0) & 0xFFU; - buffer[14U] = (pocsagFrequency >> 8) & 0xFFU; - buffer[15U] = (pocsagFrequency >> 16) & 0xFFU; - buffer[16U] = (pocsagFrequency >> 24) & 0xFFU; - - len = 17U; - } - - buffer[0U] = MMDVM_FRAME_START; - - buffer[1U] = len; - - buffer[2U] = MMDVM_SET_FREQ; - - buffer[3U] = 0x00U; - - buffer[4U] = (m_rxFrequency >> 0) & 0xFFU; - buffer[5U] = (m_rxFrequency >> 8) & 0xFFU; - buffer[6U] = (m_rxFrequency >> 16) & 0xFFU; - buffer[7U] = (m_rxFrequency >> 24) & 0xFFU; - - buffer[8U] = (m_txFrequency >> 0) & 0xFFU; - buffer[9U] = (m_txFrequency >> 8) & 0xFFU; - buffer[10U] = (m_txFrequency >> 16) & 0xFFU; - buffer[11U] = (m_txFrequency >> 24) & 0xFFU; - - // CUtils::dump(1U, "Written", buffer, len); - - int ret = m_serial->write(buffer, len); - if (ret != len) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_FREQ command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_FREQ command from the modem"); - return false; - } - - return true; -} - -RESP_TYPE_MMDVM CSerialModem::getResponse() -{ - assert(m_serial != NULL); - - if (m_state == SS_START) { - // Get the start of the frame or nothing at all - int ret = m_serial->read(m_buffer + 0U, 1U); - if (ret < 0) { - LogError("Error when reading from the modem"); - return RTM_ERROR; - } - - if (ret == 0) - return RTM_TIMEOUT; - - if (m_buffer[0U] != MMDVM_FRAME_START) - return RTM_TIMEOUT; - - m_state = SS_LENGTH1; - m_length = 1U; - } - - if (m_state == SS_LENGTH1) { - // Get the length of the frame, 1/2 - int ret = m_serial->read(m_buffer + 1U, 1U); - if (ret < 0) { - LogError("Error when reading from the modem"); - m_state = SS_START; - return RTM_ERROR; - } - - if (ret == 0) - return RTM_TIMEOUT; - - m_length = m_buffer[1U]; - m_offset = 2U; - - if (m_length == 0U) - m_state = SS_LENGTH2; - else - m_state = SS_TYPE; - } - - if (m_state == SS_LENGTH2) { - // Get the length of the frane, 2/2 - int ret = m_serial->read(m_buffer + 2U, 1U); - if (ret < 0) { - LogError("Error when reading from the modem"); - m_state = SS_START; - return RTM_ERROR; - } - - if (ret == 0) - return RTM_TIMEOUT; - - m_length = m_buffer[2U] + 255U; - m_offset = 3U; - m_state = SS_TYPE; - } - - if (m_state == SS_TYPE) { - // Get the frame type - int ret = m_serial->read(&m_type, 1U); - if (ret < 0) { - LogError("Error when reading from the modem"); - m_state = SS_START; - return RTM_ERROR; - } - - if (ret == 0) - return RTM_TIMEOUT; - - m_buffer[m_offset++] = m_type; - - m_state = SS_DATA; - } - - if (m_state == SS_DATA) { - while (m_offset < m_length) { - int ret = m_serial->read(m_buffer + m_offset, m_length - m_offset); - if (ret < 0) { - LogError("Error when reading from the modem"); - m_state = SS_START; - return RTM_ERROR; - } - - if (ret == 0) - return RTM_TIMEOUT; - - if (ret > 0) - m_offset += ret; - } - } - - // CUtils::dump(1U, "Received", m_buffer, m_length); - - m_offset = m_length > 255U ? 4U : 3U; - m_state = SS_START; - - return RTM_OK; -} - -HW_TYPE CSerialModem::getHWType() const -{ - return m_hwType; -} - -unsigned char CSerialModem::getMode() const -{ - return m_mode; -} - -bool CSerialModem::setMode(unsigned char mode) -{ - assert(m_serial != NULL); - - unsigned char buffer[4U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 4U; - buffer[2U] = MMDVM_SET_MODE; - buffer[3U] = mode; - - // CUtils::dump(1U, "Written", buffer, 4U); - - return m_serial->write(buffer, 4U) == 4; -} - -bool CSerialModem::sendCWId(const std::string& callsign) -{ - assert(m_serial != NULL); - - unsigned int length = callsign.length(); - if (length > 200U) - length = 200U; - - unsigned char buffer[205U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = length + 3U; - buffer[2U] = MMDVM_SEND_CWID; - - for (unsigned int i = 0U; i < length; i++) - buffer[i + 3U] = callsign.at(i); - - // CUtils::dump(1U, "Written", buffer, length + 3U); - - return m_serial->write(buffer, length + 3U) == int(length + 3U); -} - -bool CSerialModem::writeDMRStart(bool tx) -{ - assert(m_serial != NULL); - - if (tx && m_tx) - return true; - if (!tx && !m_tx) - return true; - - unsigned char buffer[4U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 4U; - buffer[2U] = MMDVM_DMR_START; - buffer[3U] = tx ? 0x01U : 0x00U; - - // CUtils::dump(1U, "Written", buffer, 4U); - - return m_serial->write(buffer, 4U) == 4; -} - -bool CSerialModem::writeDMRAbort(unsigned int slotNo) -{ - assert(m_serial != NULL); - - if (slotNo == 1U) - m_txDMRData1.clear(); - else - m_txDMRData2.clear(); - - unsigned char buffer[4U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 4U; - buffer[2U] = MMDVM_DMR_ABORT; - buffer[3U] = slotNo; - - // CUtils::dump(1U, "Written", buffer, 4U); - - return m_serial->write(buffer, 4U) == 4; -} - -bool CSerialModem::writeDMRShortLC(const unsigned char* lc) -{ - assert(m_serial != NULL); - assert(lc != NULL); - - unsigned char buffer[12U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 12U; - buffer[2U] = MMDVM_DMR_SHORTLC; - buffer[3U] = lc[0U]; - buffer[4U] = lc[1U]; - buffer[5U] = lc[2U]; - buffer[6U] = lc[3U]; - buffer[7U] = lc[4U]; - buffer[8U] = lc[5U]; - buffer[9U] = lc[6U]; - buffer[10U] = lc[7U]; - buffer[11U] = lc[8U]; - - // CUtils::dump(1U, "Written", buffer, 12U); - - return m_serial->write(buffer, 12U) == 12; -} - -void CSerialModem::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) -{ - m_fmCallsign = callsign; - m_fmCallsignSpeed = callsignSpeed; - m_fmCallsignFrequency = callsignFrequency; - m_fmCallsignTime = callsignTime; - m_fmCallsignHoldoff = callsignHoldoff; - m_fmCallsignHighLevel = callsignHighLevel; - m_fmCallsignLowLevel = callsignLowLevel; - m_fmCallsignAtStart = callsignAtStart; - m_fmCallsignAtEnd = callsignAtEnd; - m_fmCallsignAtLatch = callsignAtLatch; -} - -void CSerialModem::setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel) -{ - m_fmRfAck = rfAck; - m_fmAckSpeed = ackSpeed; - m_fmAckFrequency = ackFrequency; - m_fmAckMinTime = ackMinTime; - m_fmAckDelay = ackDelay; - m_fmAckLevel = ackLevel; -} - -void CSerialModem::setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, bool noiseSquelch, unsigned int squelchHighThreshold, unsigned int squelchLowThreshold, unsigned int rfAudioBoost, float maxDevLevel) -{ - m_fmTimeout = timeout; - m_fmTimeoutLevel = timeoutLevel; - - m_fmCtcssFrequency = ctcssFrequency; - m_fmCtcssHighThreshold = ctcssHighThreshold; - m_fmCtcssLowThreshold = ctcssLowThreshold; - m_fmCtcssLevel = ctcssLevel; - - m_fmKerchunkTime = kerchunkTime; - - m_fmHangTime = hangTime; - - m_fmAccessMode = accessMode; - m_fmCOSInvert = cosInvert; - - m_fmNoiseSquelch = noiseSquelch; - m_fmSquelchHighThreshold = squelchHighThreshold; - m_fmSquelchLowThreshold = squelchLowThreshold; - - m_fmRFAudioBoost = rfAudioBoost; - m_fmMaxDevLevel = maxDevLevel; -} - -void CSerialModem::setFMExtParams(const std::string& ack, unsigned int audioBoost) -{ - m_fmExtAck = ack; - m_fmExtAudioBoost = audioBoost; - m_fmExtEnable = true; -} - -bool CSerialModem::setFMCallsignParams() -{ - assert(m_serial != NULL); - - unsigned char buffer[80U]; - unsigned char len = 10U + m_fmCallsign.size(); - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = len; - buffer[2U] = MMDVM_FM_PARAMS1; - - buffer[3U] = m_fmCallsignSpeed; - buffer[4U] = m_fmCallsignFrequency / 10U; - buffer[5U] = m_fmCallsignTime; - buffer[6U] = m_fmCallsignHoldoff; - - buffer[7U] = (unsigned char)(m_fmCallsignHighLevel * 2.55F + 0.5F); - buffer[8U] = (unsigned char)(m_fmCallsignLowLevel * 2.55F + 0.5F); - - buffer[9U] = 0x00U; - if (m_fmCallsignAtStart) - buffer[9U] |= 0x01U; - if (m_fmCallsignAtEnd) - buffer[9U] |= 0x02U; - if (m_fmCallsignAtLatch) - buffer[9U] |= 0x04U; - - for (unsigned int i = 0U; i < m_fmCallsign.size(); i++) - buffer[10U + i] = m_fmCallsign.at(i); - - // CUtils::dump(1U, "Written", buffer, len); - - int ret = m_serial->write(buffer, len); - if (ret != len) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_FM_PARAMS1 command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_FM_PARAMS1 command from the modem"); - return false; - } - - return true; -} - -bool CSerialModem::setFMAckParams() -{ - assert(m_serial != NULL); - - unsigned char buffer[80U]; - unsigned char len = 8U + m_fmRfAck.size(); - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = len; - buffer[2U] = MMDVM_FM_PARAMS2; - - buffer[3U] = m_fmAckSpeed; - buffer[4U] = m_fmAckFrequency / 10U; - buffer[5U] = m_fmAckMinTime; - buffer[6U] = m_fmAckDelay / 10U; - - buffer[7U] = (unsigned char)(m_fmAckLevel * 2.55F + 0.5F); - - for (unsigned int i = 0U; i < m_fmRfAck.size(); i++) - buffer[8U + i] = m_fmRfAck.at(i); - - // CUtils::dump(1U, "Written", buffer, len); - - int ret = m_serial->write(buffer, len); - if (ret != len) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_FM_PARAMS2 command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_FM_PARAMS2 command from the modem"); - return false; - } - - return true; -} - -bool CSerialModem::setFMMiscParams() -{ - assert(m_serial != NULL); - - unsigned char buffer[20U]; - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = 17U; - buffer[2U] = MMDVM_FM_PARAMS3; - - buffer[3U] = m_fmTimeout / 5U; - buffer[4U] = (unsigned char)(m_fmTimeoutLevel * 2.55F + 0.5F); - - buffer[5U] = (unsigned char)m_fmCtcssFrequency; - buffer[6U] = m_fmCtcssHighThreshold; - buffer[7U] = m_fmCtcssLowThreshold; - buffer[8U] = (unsigned char)(m_fmCtcssLevel * 2.55F + 0.5F); - - buffer[9U] = m_fmKerchunkTime; - buffer[10U] = m_fmHangTime; - - buffer[11U] = m_fmAccessMode & 0x0FU; - if (m_fmNoiseSquelch) - buffer[11U] |= 0x40U; - if (m_fmCOSInvert) - buffer[11U] |= 0x80U; - - buffer[12U] = m_fmRFAudioBoost; - - buffer[13U] = (unsigned char)(m_fmMaxDevLevel * 2.55F + 0.5F); - - buffer[14U] = (unsigned char)(m_rxLevel * 2.55F + 0.5F); - - buffer[15U] = m_fmSquelchHighThreshold; - buffer[16U] = m_fmSquelchLowThreshold; - - // CUtils::dump(1U, "Written", buffer, 17U); - - int ret = m_serial->write(buffer, 17U); - if (ret != 17) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_FM_PARAMS3 command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_FM_PARAMS3 command from the modem"); - return false; - } - - return true; -} - -bool CSerialModem::setFMExtParams() -{ - assert(m_serial != NULL); - - unsigned char buffer[80U]; - unsigned char len = 7U + m_fmExtAck.size(); - - buffer[0U] = MMDVM_FRAME_START; - buffer[1U] = len; - buffer[2U] = MMDVM_FM_PARAMS4; - - buffer[3U] = m_fmExtAudioBoost; - buffer[4U] = m_fmAckSpeed; - buffer[5U] = m_fmAckFrequency / 10U; - - buffer[6U] = (unsigned char)(m_fmAckLevel * 2.55F + 0.5F); - - for (unsigned int i = 0U; i < m_fmExtAck.size(); i++) - buffer[7U + i] = m_fmExtAck.at(i); - - // CUtils::dump(1U, "Written", buffer, len); - - int ret = m_serial->write(buffer, len); - if (ret != len) - return false; - - unsigned int count = 0U; - RESP_TYPE_MMDVM resp; - do { - CThread::sleep(10U); - - resp = getResponse(); - if (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK) { - count++; - if (count >= MAX_RESPONSES) { - LogError("The MMDVM is not responding to the SET_FM_PARAMS4 command"); - return false; - } - } - } while (resp == RTM_OK && m_buffer[2U] != MMDVM_ACK && m_buffer[2U] != MMDVM_NAK); - - // CUtils::dump(1U, "Response", m_buffer, m_length); - - if (resp == RTM_OK && m_buffer[2U] == MMDVM_NAK) { - LogError("Received a NAK to the SET_FM_PARAMS4 command from the modem"); - return false; - } - - return true; -} - -void CSerialModem::printDebug() -{ - if (m_buffer[2U] == MMDVM_DEBUG1) { - LogMessage("Debug: %.*s", m_length - m_offset - 0U, m_buffer + m_offset); - } else if (m_buffer[2U] == MMDVM_DEBUG2) { - short val1 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; - LogMessage("Debug: %.*s %d", m_length - m_offset - 2U, m_buffer + m_offset, val1); - } else if (m_buffer[2U] == MMDVM_DEBUG3) { - short val1 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; - short val2 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; - LogMessage("Debug: %.*s %d %d", m_length - m_offset - 4U, m_buffer + m_offset, val1, val2); - } else if (m_buffer[2U] == MMDVM_DEBUG4) { - short val1 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; - short val2 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; - short val3 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; - LogMessage("Debug: %.*s %d %d %d", m_length - m_offset - 6U, m_buffer + m_offset, val1, val2, val3); - } else if (m_buffer[2U] == MMDVM_DEBUG5) { - short val1 = (m_buffer[m_length - 8U] << 8) | m_buffer[m_length - 7U]; - short val2 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; - short val3 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; - short val4 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; - LogMessage("Debug: %.*s %d %d %d %d", m_length - m_offset - 8U, m_buffer + m_offset, val1, val2, val3, val4); - } -} diff --git a/SerialModem.h b/SerialModem.h deleted file mode 100644 index 4b56077..0000000 --- a/SerialModem.h +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2011-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 SERIALMODEM_H -#define SERIALMODEM_H - -#include "Modem.h" - -#include "SerialPort.h" -#include "RingBuffer.h" -#include "Defines.h" -#include "Timer.h" - -#include - -enum RESP_TYPE_MMDVM { - RTM_OK, - RTM_TIMEOUT, - RTM_ERROR -}; - -enum SERIAL_STATE { - SS_START, - SS_LENGTH1, - SS_LENGTH2, - SS_TYPE, - SS_DATA -}; - -class CSerialModem : public IModem { -public: - CSerialModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); - virtual ~CSerialModem(); - - virtual void setSerialParams(const std::string& protocol, unsigned int address, unsigned int speed); - 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 m17Enabled, bool pocsagEnabled, bool fmEnabled, bool ax25Enabled); - virtual void setLevels(float rxLevel, float cwIdTXLevel, float dstarTXLevel, float dmrTXLevel, float ysfTXLevel, float p25TXLevel, float nxdnTXLevel, float m17TXLevel, float pocsagLevel, float fmTXLevel, float ax25TXLevel); - 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 setAX25Params(int rxTwist, unsigned int txDelay, unsigned int slotTime, unsigned int pPersist); - 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); - virtual void setFMAckParams(const std::string& rfAck, unsigned int ackSpeed, unsigned int ackFrequency, unsigned int ackMinTime, unsigned int ackDelay, float ackLevel); - virtual void setFMMiscParams(unsigned int timeout, float timeoutLevel, float ctcssFrequency, unsigned int ctcssHighThreshold, unsigned int ctcssLowThreshold, float ctcssLevel, unsigned int kerchunkTime, unsigned int hangTime, unsigned int accessMode, bool cosInvert, bool noiseSquelch, unsigned int squelchHighThreshold, unsigned int squelchLowThreshold, unsigned int rfAudioBoost, float maxDevLevel); - virtual void setFMExtParams(const std::string& ack, unsigned int audioBoost); - - virtual bool open(); - - virtual unsigned int readDStarData(unsigned char* data); - virtual unsigned int readDMRData1(unsigned char* data); - virtual unsigned int readDMRData2(unsigned char* data); - 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 readFMData(unsigned char* data); - virtual unsigned int readAX25Data(unsigned char* data); - - virtual bool hasDStarSpace() const; - virtual bool hasDMRSpace1() const; - virtual bool hasDMRSpace2() const; - virtual bool hasYSFSpace() const; - virtual bool hasP25Space() const; - virtual bool hasNXDNSpace() const; - virtual bool hasM17Space() const; - virtual bool hasPOCSAGSpace() const; - virtual unsigned int getFMSpace() const; - virtual bool hasAX25Space() const; - - virtual bool hasTX() const; - virtual bool hasCD() const; - - virtual bool hasLockout() const; - virtual bool hasError() const; - - virtual bool writeConfig(); - virtual bool writeDStarData(const unsigned char* data, unsigned int length); - virtual bool writeDMRData1(const unsigned char* data, unsigned int length); - virtual bool writeDMRData2(const unsigned char* data, unsigned int length); - 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 writeFMData(const unsigned char* data, unsigned int length); - virtual bool writeAX25Data(const unsigned char* data, unsigned int length); - - virtual bool writeDStarInfo(const char* my1, const char* my2, const char* your, const char* type, const char* reflector); - virtual bool writeDMRInfo(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type); - 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); - - virtual bool writeDMRStart(bool tx); - virtual bool writeDMRShortLC(const unsigned char* lc); - virtual bool writeDMRAbort(unsigned int slotNo); - - virtual bool writeTransparentData(const unsigned char* data, unsigned int length); - virtual unsigned int readTransparentData(unsigned char* data); - - virtual bool writeSerial(const unsigned char* data, unsigned int length); - virtual unsigned int readSerial(unsigned char* data, unsigned int length); - - virtual unsigned char getMode() const; - virtual bool setMode(unsigned char mode); - - virtual bool sendCWId(const std::string& callsign); - - virtual HW_TYPE getHWType() const; - - virtual void clock(unsigned int ms); - - virtual void close(); - -private: - std::string m_port; - unsigned int m_protocolVersion; - unsigned int m_dmrColorCode; - bool m_ysfLoDev; - 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; - bool m_pttInvert; - unsigned int m_txDelay; - unsigned int m_dmrDelay; - float m_rxLevel; - float m_cwIdTXLevel; - float m_dstarTXLevel; - float m_dmrTXLevel; - float m_ysfTXLevel; - float m_p25TXLevel; - float m_nxdnTXLevel; - float m_m17TXLevel; - float m_pocsagTXLevel; - float m_fmTXLevel; - float m_ax25TXLevel; - float m_rfLevel; - bool m_useCOSAsLockout; - bool m_trace; - bool m_debug; - unsigned int m_rxFrequency; - unsigned int m_txFrequency; - unsigned int m_pocsagFrequency; - bool m_dstarEnabled; - bool m_dmrEnabled; - bool m_ysfEnabled; - bool m_p25Enabled; - bool m_nxdnEnabled; - bool m_m17Enabled; - bool m_pocsagEnabled; - bool m_fmEnabled; - bool m_ax25Enabled; - int m_rxDCOffset; - int m_txDCOffset; - ISerialPort* m_serial; - unsigned char* m_buffer; - unsigned int m_length; - unsigned int m_offset; - SERIAL_STATE m_state; - unsigned char m_type; - CRingBuffer m_rxDStarData; - CRingBuffer m_txDStarData; - CRingBuffer m_rxDMRData1; - CRingBuffer m_rxDMRData2; - CRingBuffer m_txDMRData1; - CRingBuffer m_txDMRData2; - CRingBuffer m_rxYSFData; - CRingBuffer m_txYSFData; - CRingBuffer m_rxP25Data; - CRingBuffer m_txP25Data; - CRingBuffer m_rxNXDNData; - CRingBuffer m_txNXDNData; - CRingBuffer m_rxM17Data; - CRingBuffer m_txM17Data; - CRingBuffer m_txPOCSAGData; - CRingBuffer m_rxFMData; - CRingBuffer m_txFMData; - CRingBuffer m_rxAX25Data; - CRingBuffer m_txAX25Data; - CRingBuffer m_rxSerialData; - CRingBuffer m_txSerialData; - CRingBuffer m_rxTransparentData; - CRingBuffer m_txTransparentData; - unsigned int m_sendTransparentDataFrameType; - CTimer m_statusTimer; - CTimer m_inactivityTimer; - CTimer m_playoutTimer; - unsigned int m_dstarSpace; - unsigned int m_dmrSpace1; - unsigned int m_dmrSpace2; - unsigned int m_ysfSpace; - unsigned int m_p25Space; - unsigned int m_nxdnSpace; - unsigned int m_m17Space; - unsigned int m_pocsagSpace; - unsigned int m_fmSpace; - unsigned int m_ax25Space; - bool m_tx; - bool m_cd; - bool m_lockout; - bool m_error; - unsigned char m_mode; - HW_TYPE m_hwType; - int m_ax25RXTwist; - unsigned int m_ax25TXDelay; - unsigned int m_ax25SlotTime; - unsigned int m_ax25PPersist; - - std::string m_fmCallsign; - unsigned int m_fmCallsignSpeed; - unsigned int m_fmCallsignFrequency; - unsigned int m_fmCallsignTime; - unsigned int m_fmCallsignHoldoff; - float m_fmCallsignHighLevel; - float m_fmCallsignLowLevel; - bool m_fmCallsignAtStart; - bool m_fmCallsignAtEnd; - bool m_fmCallsignAtLatch; - std::string m_fmRfAck; - std::string m_fmExtAck; - unsigned int m_fmAckSpeed; - unsigned int m_fmAckFrequency; - unsigned int m_fmAckMinTime; - unsigned int m_fmAckDelay; - float m_fmAckLevel; - unsigned int m_fmTimeout; - float m_fmTimeoutLevel; - float m_fmCtcssFrequency; - unsigned int m_fmCtcssHighThreshold; - unsigned int m_fmCtcssLowThreshold; - float m_fmCtcssLevel; - unsigned int m_fmKerchunkTime; - unsigned int m_fmHangTime; - unsigned int m_fmAccessMode; - bool m_fmCOSInvert; - bool m_fmNoiseSquelch; - unsigned int m_fmSquelchHighThreshold; - unsigned int m_fmSquelchLowThreshold; - unsigned int m_fmRFAudioBoost; - unsigned int m_fmExtAudioBoost; - float m_fmMaxDevLevel; - bool m_fmExtEnable; - - bool readVersion(); - bool readStatus(); - bool setConfig1(); - bool setConfig2(); - bool setFrequency(); - bool setFMCallsignParams(); - bool setFMAckParams(); - bool setFMMiscParams(); - bool setFMExtParams(); - - void printDebug(); - - RESP_TYPE_MMDVM getResponse(); -}; - -#endif diff --git a/Sync.cpp b/Sync.cpp index 3e53f64..75156af 100644 --- a/Sync.cpp +++ b/Sync.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,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 @@ -23,7 +23,6 @@ #include "YSFDefines.h" #include "P25Defines.h" #include "NXDNDefines.h" -#include "M17Defines.h" #include #include @@ -84,17 +83,3 @@ 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::addM17HeaderSync(unsigned char* data) -{ - assert(data != NULL); - - ::memcpy(data, M17_HEADER_SYNC_BYTES, M17_SYNC_LENGTH_BYTES); -} - -void CSync::addM17DataSync(unsigned char* data) -{ - assert(data != NULL); - - ::memcpy(data, M17_DATA_SYNC_BYTES, M17_SYNC_LENGTH_BYTES); -} diff --git a/Sync.h b/Sync.h index 20ee2dd..3ff325c 100644 --- a/Sync.h +++ b/Sync.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,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 @@ -33,9 +33,6 @@ public: static void addNXDNSync(unsigned char* data); - static void addM17HeaderSync(unsigned char* data); - static void addM17DataSync(unsigned char* data); - private: }; diff --git a/TFTSerial.cpp b/TFTSerial.cpp new file mode 100644 index 0000000..dab532e --- /dev/null +++ b/TFTSerial.cpp @@ -0,0 +1,588 @@ +/* + * 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 + * 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 "TFTSerial.h" +#include "Log.h" + +#include +#include +#include + +const unsigned char ROTATION_PORTRAIT_LEFT = 0U; +const unsigned char ROTATION_LANDSCAPE_UD = 1U; +const unsigned char ROTATION_PORTRAIT_RIGHT = 2U; +const unsigned char ROTATION_LANDSCAPE = 3U; + +const unsigned char COLOUR_BLACK = 0U; +const unsigned char COLOUR_BLUE = 1U; +const unsigned char COLOUR_RED = 2U; +const unsigned char COLOUR_GREEN = 3U; +const unsigned char COLOUR_CYAN = 4U; +const unsigned char COLOUR_MAGENTA = 5U; +const unsigned char COLOUR_YELLOW = 6U; +const unsigned char COLOUR_WHITE = 7U; + +const unsigned char FONT_SMALL = 1U; +const unsigned char FONT_MEDIUM = 2U; +const unsigned char FONT_LARGE = 3U; + +// x = 0 to 159, y = 0 to 127 - Landscape +// x = 0 to 127, y = 0 to 159 - Portrait + +CTFTSerial::CTFTSerial(const std::string& callsign, unsigned int dmrid, ISerialPort* serial, unsigned int brightness) : +CDisplay(), +m_callsign(callsign), +m_dmrid(dmrid), +m_serial(serial), +m_brightness(brightness), +m_mode(MODE_IDLE) +{ + assert(serial != NULL); + assert(brightness >= 0U && brightness <= 100U); +} + +CTFTSerial::~CTFTSerial() +{ +} + +bool CTFTSerial::open() +{ + bool ret = m_serial->open(); + if (!ret) { + LogError("Cannot open the port for the TFT Serial"); + delete m_serial; + return false; + } + + setRotation(ROTATION_LANDSCAPE); + + setBrightness(m_brightness); + + setBackground(COLOUR_WHITE); + + setForeground(COLOUR_BLACK); + + setIdle(); + + return true; +} + +void CTFTSerial::setIdleInt() +{ + // Clear the screen + clearScreen(); + + setFontSize(FONT_LARGE); + + // Draw MMDVM logo + displayBitmap(0U, 0U, "MMDVM_sm.bmp"); + + char text[30]; + ::sprintf(text, "%-6s / %u", m_callsign.c_str(), m_dmrid); + + gotoPosPixel(18U, 55U); + displayText(text); + + gotoPosPixel(45U, 90U); + displayText("IDLE"); + + m_mode = MODE_IDLE; +} + +void CTFTSerial::setErrorInt(const char* text) +{ + assert(text != NULL); + + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw MMDVM logo + displayBitmap(0U, 0U, "MMDVM_sm.bmp"); + + setForeground(COLOUR_RED); + + gotoPosPixel(18U, 55U); + displayText(text); + + gotoPosPixel(18U, 90U); + displayText("ERROR"); + + setForeground(COLOUR_BLACK); + + m_mode = MODE_ERROR; +} + +void CTFTSerial::setLockoutInt() +{ + // Clear the screen + clearScreen(); + + setFontSize(FONT_LARGE); + + // Draw MMDVM logo + displayBitmap(0U, 0U, "MMDVM_sm.bmp"); + + gotoPosPixel(20U, 60U); + displayText("LOCKOUT"); + + m_mode = MODE_LOCKOUT; +} + +void CTFTSerial::setQuitInt() +{ + // Clear the screen + clearScreen(); + + setFontSize(FONT_LARGE); + + // Draw MMDVM logo + displayBitmap(0U, 0U, "MMDVM_sm.bmp"); + + gotoPosPixel(20U, 60U); + displayText("STOPPED"); + + m_mode = MODE_QUIT; +} + +void CTFTSerial::setFMInt() +{ + // Clear the screen + clearScreen(); + + setFontSize(FONT_LARGE); + + // Draw MMDVM logo + displayBitmap(0U, 0U, "MMDVM_sm.bmp"); + + gotoPosPixel(20U, 60U); + displayText("FM"); + + m_mode = MODE_FM; +} + +void CTFTSerial::writeDStarInt(const char* my1, const char* my2, const char* your, const char* type, const char* reflector) +{ + assert(my1 != NULL); + assert(my2 != NULL); + assert(your != NULL); + assert(type != NULL); + assert(reflector != NULL); + + if (m_mode != MODE_DSTAR) { + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw D-Star insignia + displayBitmap(0U, 0U, "DStar_sm.bmp"); + } + + char text[30U]; + + ::sprintf(text, "%s %.8s/%4.4s", type, my1, my2); + gotoPosPixel(5U, 70U); + displayText(text); + + ::sprintf(text, "%.8s", your); + gotoPosPixel(5U, 90U); + displayText(text); + + if (::strcmp(reflector, " ") != 0) { + ::sprintf(text, "via %.8s", reflector); + gotoPosPixel(5U, 110U); + displayText(text); + } else { + gotoPosPixel(5U, 110U); + displayText(" "); + } + + m_mode = MODE_DSTAR; +} + +void CTFTSerial::clearDStarInt() +{ + gotoPosPixel(5U, 70U); + displayText(" Listening "); + + gotoPosPixel(5U, 90U); + displayText(" "); + + gotoPosPixel(5U, 110U); + displayText(" "); +} + +void CTFTSerial::writeDMRInt(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type) +{ + assert(type != NULL); + + if (m_mode != MODE_DMR) { + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw DMR insignia + displayBitmap(0U, 0U, "DMR_sm.bmp"); + + if (slotNo == 1U) { + gotoPosPixel(5U, 90U); + displayText("2 Listening"); + } else { + gotoPosPixel(5U, 55U); + displayText("1 Listening"); + } + } + + if (slotNo == 1U) { + char text[30U]; + + ::sprintf(text, "1 %s %s", type, src.c_str()); + gotoPosPixel(5U, 55U); + displayText(text); + + ::sprintf(text, "%s%s", group ? "TG" : "", dst.c_str()); + gotoPosPixel(65U, 72U); + displayText(text); + } else { + char text[30U]; + + ::sprintf(text, "2 %s %s", type, src.c_str()); + gotoPosPixel(5U, 90U); + displayText(text); + + ::sprintf(text, "%s%s", group ? "TG" : "", dst.c_str()); + gotoPosPixel(65U, 107U); + displayText(text); + } + + m_mode = MODE_DMR; +} + +void CTFTSerial::clearDMRInt(unsigned int slotNo) +{ + if (slotNo == 1U) { + gotoPosPixel(5U, 55U); + displayText("1 Listening "); + + gotoPosPixel(65U, 72U); + displayText(" "); + } else { + gotoPosPixel(5U, 90U); + displayText("2 Listening "); + + gotoPosPixel(65U, 107U); + displayText(" "); + } +} + +void CTFTSerial::writeFusionInt(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin) +{ + assert(source != NULL); + assert(dest != NULL); + assert(type != NULL); + assert(origin != NULL); + + if (m_mode != MODE_YSF) { + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw the System Fusion insignia + displayBitmap(0U, 0U, "YSF_sm.bmp"); + } + + char text[30U]; + ::sprintf(text, "%s %.10s", type, source); + + gotoPosPixel(5U, 70U); + displayText(text); + + ::sprintf(text, " DG-ID %u", dgid); + + gotoPosPixel(5U, 90U); + displayText(text); + + if (::strcmp(origin, " ") != 0) { + ::sprintf(text, "at %.10s", origin); + gotoPosPixel(5U, 110U); + displayText(text); + } else { + gotoPosPixel(5U, 110U); + displayText(" "); + } + + m_mode = MODE_YSF; +} + +void CTFTSerial::clearFusionInt() +{ + gotoPosPixel(5U, 70U); + displayText(" Listening "); + + gotoPosPixel(5U, 90U); + displayText(" "); + + gotoPosPixel(5U, 110U); + displayText(" "); +} + +void CTFTSerial::writeP25Int(const char* source, bool group, unsigned int dest, const char* type) +{ + assert(source != NULL); + assert(type != NULL); + + if (m_mode != MODE_P25) { + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw the P25 insignia + displayBitmap(0U, 0U, "P25_sm.bmp"); + } + + char text[30U]; + ::sprintf(text, "%s %.10s", type, source); + + gotoPosPixel(5U, 70U); + displayText(text); + + ::sprintf(text, " %s%u", group ? "TG" : "", dest); + + gotoPosPixel(5U, 90U); + displayText(text); + + m_mode = MODE_P25; +} + +void CTFTSerial::clearP25Int() +{ + gotoPosPixel(5U, 70U); + displayText(" Listening "); + + gotoPosPixel(5U, 90U); + displayText(" "); + + gotoPosPixel(5U, 110U); + displayText(" "); +} + +void CTFTSerial::writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type) +{ + assert(source != NULL); + assert(type != NULL); + + if (m_mode != MODE_NXDN) { + // Clear the screen + clearScreen(); + + setFontSize(FONT_MEDIUM); + + // Draw the P25 insignia + displayBitmap(0U, 0U, "NXDN_sm.bmp"); + } + + char text[30U]; + ::sprintf(text, "%s %.10s", type, source); + + gotoPosPixel(5U, 70U); + displayText(text); + + ::sprintf(text, " %s%u", group ? "TG" : "", dest); + + gotoPosPixel(5U, 90U); + displayText(text); + + m_mode = MODE_NXDN; +} + +void CTFTSerial::clearNXDNInt() +{ + gotoPosPixel(5U, 70U); + displayText(" Listening "); + + gotoPosPixel(5U, 90U); + displayText(" "); + + gotoPosPixel(5U, 110U); + displayText(" "); +} + +void CTFTSerial::writePOCSAGInt(uint32_t ric, const std::string& message) +{ + gotoPosPixel(15U, 90U); + displayText("POCSAG TX"); + + m_mode = MODE_CW; +} + +void CTFTSerial::clearPOCSAGInt() +{ + gotoPosPixel(45U, 90U); + displayText("IDLE"); +} + +void CTFTSerial::writeCWInt() +{ + gotoPosPixel(45U, 90U); + displayText("CW TX"); + + m_mode = MODE_CW; +} + +void CTFTSerial::clearCWInt() +{ + gotoPosPixel(45U, 90U); + displayText("IDLE"); +} + +void CTFTSerial::close() +{ + m_serial->close(); + delete m_serial; +} + +void CTFTSerial::clearScreen() +{ + m_serial->write((unsigned char*)"\x1B\x00\xFF", 3U); +} + +void CTFTSerial::setForeground(unsigned char colour) +{ + assert(colour >= 0U && colour <= 7U); + + m_serial->write((unsigned char*)"\x1B\x01", 2U); + m_serial->write(&colour, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::setBackground(unsigned char colour) +{ + assert(colour >= 0U && colour <= 7U); + + m_serial->write((unsigned char*)"\x1B\x02", 2U); + m_serial->write(&colour, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::setRotation(unsigned char rotation) +{ + assert(rotation >= 0U && rotation <= 3U); + + m_serial->write((unsigned char*)"\x1B\x03", 2U); + m_serial->write(&rotation, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::setFontSize(unsigned char size) +{ + assert(size >= 1U && size <= 3U); + + m_serial->write((unsigned char*)"\x1B\x04", 2U); + m_serial->write(&size, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::gotoBegOfLine() +{ + m_serial->write((unsigned char*)"\x1B\x05\xFF", 3U); +} + +void CTFTSerial::gotoPosText(unsigned char x, unsigned char y) +{ + m_serial->write((unsigned char*)"\x1B\x06", 2U); + m_serial->write(&x, 1U); + m_serial->write(&y, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::gotoPosPixel(unsigned char x, unsigned char y) +{ + m_serial->write((unsigned char*)"\x1B\x07", 2U); + m_serial->write(&x, 1U); + m_serial->write(&y, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::drawLine(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2) +{ + m_serial->write((unsigned char*)"\x1B\x08", 2U); + m_serial->write(&x1, 1U); + m_serial->write(&y1, 1U); + m_serial->write(&x2, 1U); + m_serial->write(&y2, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::drawBox(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, bool filled) +{ + if (filled) + m_serial->write((unsigned char*)"\x1B\x0A", 2U); + else + m_serial->write((unsigned char*)"\x1B\x09", 2U); + + m_serial->write(&x1, 1U); + m_serial->write(&y1, 1U); + m_serial->write(&x2, 1U); + m_serial->write(&y2, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::drawCircle(unsigned char x, unsigned char y, unsigned char radius, bool filled) +{ + if (filled) + m_serial->write((unsigned char*)"\x1B\x0C", 2U); + else + m_serial->write((unsigned char*)"\x1B\x0B", 2U); + + m_serial->write(&x, 1U); + m_serial->write(&y, 1U); + m_serial->write(&radius, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::displayBitmap(unsigned char x, unsigned char y, const char* filename) +{ + assert(filename != NULL); + + m_serial->write((unsigned char*)"\x1B\x0D", 2U); + m_serial->write(&x, 1U); + m_serial->write(&y, 1U); + m_serial->write((unsigned char*)filename, (unsigned int)::strlen(filename)); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::setBrightness(unsigned char brightness) +{ + assert(brightness >= 0U && brightness <= 100U); + + m_serial->write((unsigned char*)"\x1B\x0E", 2U); + m_serial->write(&brightness, 1U); + m_serial->write((unsigned char*)"\xFF", 1U); +} + +void CTFTSerial::displayText(const char* text) +{ + assert(text != NULL); + + m_serial->write((unsigned char*)text, (unsigned int)::strlen(text)); +} diff --git a/TFTSerial.h b/TFTSerial.h new file mode 100644 index 0000000..d34e9da --- /dev/null +++ b/TFTSerial.h @@ -0,0 +1,89 @@ +/* + * 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 + * 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(TFTSERIAL_H) +#define TFTSERIAL_H + +#include "Display.h" +#include "Defines.h" +#include "SerialPort.h" + +#include + +class CTFTSerial : public CDisplay +{ +public: + CTFTSerial(const std::string& callsign, unsigned int dmrid, ISerialPort* serial, unsigned int brightness); + virtual ~CTFTSerial(); + + virtual bool open(); + + virtual void close(); + +protected: + virtual void setIdleInt(); + virtual void setErrorInt(const char* text); + virtual void setLockoutInt(); + virtual void setQuitInt(); + virtual void setFMInt(); + + virtual void writeDStarInt(const char* my1, const char* my2, const char* your, const char* type, const char* reflector); + virtual void clearDStarInt(); + + virtual void writeDMRInt(unsigned int slotNo, const std::string& src, bool group, const std::string& dst, const char* type); + virtual void clearDMRInt(unsigned int slotNo); + + virtual void writeFusionInt(const char* source, const char* dest, unsigned char dgid, const char* type, const char* origin); + virtual void clearFusionInt(); + + virtual void writeP25Int(const char* source, bool group, unsigned int dest, const char* type); + virtual void clearP25Int(); + + virtual void writeNXDNInt(const char* source, bool group, unsigned int dest, const char* type); + virtual void clearNXDNInt(); + + virtual void writePOCSAGInt(uint32_t ric, const std::string& message); + virtual void clearPOCSAGInt(); + + virtual void writeCWInt(); + virtual void clearCWInt(); + +private: + std::string m_callsign; + unsigned int m_dmrid; + ISerialPort* m_serial; + unsigned int m_brightness; + unsigned char m_mode; + + void clearScreen(); + void setBackground(unsigned char colour); + void setForeground(unsigned char colour); + void setRotation(unsigned char rotation); + void setFontSize(unsigned char size); + void gotoBegOfLine(); + void gotoPosText(unsigned char x, unsigned char y); + void gotoPosPixel(unsigned char x, unsigned char y); + void drawLine(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2); + void drawBox(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, bool filled); + void drawCircle(unsigned char x, unsigned char y, unsigned char radius, bool filled); + void displayBitmap(unsigned char x, unsigned char y, const char* filename); + void setBrightness(unsigned char brightness); + void displayText(const char* text); +}; + +#endif diff --git a/TFTSerial/DMR_sm.bmp b/TFTSerial/DMR_sm.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2fae46a3c491253e54cf05c22140722c0475c53a GIT binary patch literal 24054 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 literal 0 HcmV?d00001 diff --git a/TFTSerial/DStar_sm.bmp b/TFTSerial/DStar_sm.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f4e2afc1982b92f99f9fba2ee3b1fc9ff86ee394 GIT binary patch literal 24054 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 literal 0 HcmV?d00001 diff --git a/TFTSerial/NXDN_sm.bmp b/TFTSerial/NXDN_sm.bmp new file mode 100644 index 0000000000000000000000000000000000000000..30420d87fc103c16695e4786c48af97583df7c27 GIT binary patch literal 23574 zcmeHOXN*o;6g4D7jZV}E(G$J*r-tZ6@1hH$L??(|Lv*4OB3g(N3DJJ&f+z{2cR}>_ z-r^)@&g^r~y7#+Y=Er;YOJ-)Dz1LcM-}8OVc>Sq8^BPAnJktN)LSh z{yhmU<(_mYQGl~&&k}iG zk;|7a6Z-V&(|r|u0Yo2fAE(!11yLISKjONXoo3ge&Y}hcDe%`!!ZM^Am5w-v6)2Ajr zgnjDNDXnH3L~!%w%{E?mmM>o}6msTN+a3a#(8QOupr~e}MvW3cUe=K#M+DIe71FnF z-##I(n~xqnBBCEYe9%Z?@SaziqeqVxtPo97-o}j^U4rGymnTFODpI6~P0yDvA18kM z_RVH-3ZgcpNs}hNtl)qF0|wYBlqX}xj4qw2Q>VIEZQ8VPv9v#R>eMcJaDTUM-L#-E zN|!F}(h*2k)AN2TSg@cAulyqEty{M^zG1_Lf`aDJqenR_)@XwU4eTuNOqw*wC55jf zPYRqaityjQecPoF(0}F16`^T1fByVw>#SV4(!>yZ_Uzd<9Uk1;goTjFnmlPx9o+O-<8Yx^$_nR=jv|6GQCW zxpUie&J>r2wO;qd|;ImoC`~ zF?sIXxkD0(LE!xP^9k4vjUPXr6jQO{ucJk-SN81LeZNMxZrw`sUCPSuB4U*zCL&+M zxq0)ZnP}(Eom^Z*yL9Pd(!<$gb!f(n8Kx$tb)o>?#L9+Ax9#fHt8)b&wFONNzZo%2 zHo>M%Rt8Ydh!G=9Fzlj5f1Q8xeD&+s&jg29dok(Z>~Z79dDaFeCaqc%_p%E{{P*wQ z*9x9SojP?0orJ_HnJv!8-Zg91v?R%HU&86U=2jqbE+BFcm`B7-M)UE9c9X zFO?=HbOh!K4H+`Tx1{>@>$~X0zk2m*Uu80SgfgK|n>LN49y4Z);1ck;bLSLz!-fq6 z<_fun3Zeks%$6;iOMmRxv5HIP@-D9O^VBLDVnrf|ES{yLHnN5TaY95$WUo-6f(zfN zQzr$+l7b7v{UTS0=B!z>Tq*GF3_*U6^DnHcSFZ*siHnHxiWMssMDy9RXM$)pOO+}` zMB27(Yl4L>)-AVf+a>^JQ)Mwd!meGrHfDvwshj)v?IPD zZX_Sh9zA*lsR5Xd9Xo1GZme0eCZHzY+8`o)3$JqJ%7RM@K7aoFx^?Ra0MoA4ty{YQ z7@~+=A(~|65TocEIdZtN!;f^UR;}WIfF+eIS(51J(6VLA0t!xwM90RP z9zm>Ix2_pA?cTk6SE*9P^avYL*d-B8vng_gXkxX>6bx?n>C;E0Bb3vpPX{cCh=>qI ztwA)g^C*aB6Fd7vgg>Hp?b>B>4;(mPDhIbQyi_`b!e{zs8N?>~B!u-nrAgK+uwz2< z>d>)c$4DYOgolRa5+i^9{5Ds6#2SvNgM%mPZ*r~TgFw@#fukRTwlNMgPIT9T!&#ke*DN&>%|{^ zQ5#;aGa6cRHUQWd$2`3$sY&z?AEg{K6bkX5TzB`M6%yLa#G zD>4Dx#wN8*A3AiXi-lDP8y~BuY15`IebbNqSx@DYCr_F%VS7^G#L)|$iImPoX#^;2yVv6GYv#3fpalXh`>e{ty8ZV5A6DPXpY0{(-;1ErG@4NJs zA1gvcmLJQ+UWVZE%~oW*d-txOpvm`!z_Rb+;Aa>4^yR!{T6e4z^LcJNV7;BEaXyRUC87r8>dfQ z3cg%h&Gxv&mH+eS&o;&MU@YoV5X*YL$9~F|Eo&+Z+xM1SxNxCKiD{SFz64Cr#F05u z+_W=i&TP}Q2P>Z@*tT(cT!U@H#>eWx_ZObh*t!+3u3fthA3oey?ZJZw*i#T()ojtC z1)1GUDd1lZ%*Q)!d6Lc+!J-Ntw%AVc>Sq8^BPAnJjr2cjN`dLZh7 Ks0aSfJ@5}d(?}!$ literal 0 HcmV?d00001 diff --git a/TFTSerial/P25_sm.bmp b/TFTSerial/P25_sm.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5ba24f609b837d732147cbb0704ec38d481ab7d8 GIT binary patch literal 24054 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 literal 0 HcmV?d00001 diff --git a/TFTSurenoo.cpp b/TFTSurenoo.cpp index c9498b6..fc61ca9 100644 --- a/TFTSurenoo.cpp +++ b/TFTSurenoo.cpp @@ -77,7 +77,6 @@ enum LcdColour { #define STR_DSTAR "D-STAR" #define STR_MMDVM "MMDVM" #define STR_NXDN "NXDN" -#define STR_M17 "M17" #define STR_P25 "P25" #define STR_YSF "SystemFusion" @@ -359,29 +358,6 @@ void CTFTSurenoo::clearNXDNInt() clearDStarInt(); } -void CTFTSurenoo::writeM17Int(const char* source, const char* dest, const char* type) -{ - assert(source != NULL); - assert(dest != NULL); - assert(type != NULL); - - if (m_mode != MODE_M17) - setModeLine(STR_M17); - - ::snprintf(m_temp, sizeof(m_temp), "%s %s", type, source); - setStatusLine(statusLineNo(0), m_temp); - - ::snprintf(m_temp, sizeof(m_temp), "%s", dest); - setStatusLine(statusLineNo(1), m_temp); - - m_mode = MODE_M17; -} - -void CTFTSurenoo::clearM17Int() -{ - clearDStarInt(); -} - void CTFTSurenoo::writePOCSAGInt(uint32_t ric, const std::string& message) { setStatusLine(statusLineNo(1), "POCSAG TX"); diff --git a/TFTSurenoo.h b/TFTSurenoo.h index 2e9abdb..e21a17e 100644 --- a/TFTSurenoo.h +++ b/TFTSurenoo.h @@ -61,9 +61,6 @@ protected: virtual int writeNXDNIntEx(const class CUserDBentry& source, bool group, unsigned int dest, const char* type); virtual void clearNXDNInt(); - virtual void writeM17Int(const char* source, const char* dest, const char* type); - virtual void clearM17Int(); - virtual void writePOCSAGInt(uint32_t ric, const std::string& message); virtual void clearPOCSAGInt(); diff --git a/Tools/DeEmphasis.py b/Tools/DeEmphasis.py deleted file mode 100644 index 44320c9..0000000 --- a/Tools/DeEmphasis.py +++ /dev/null @@ -1,43 +0,0 @@ -#based on https://github.com/gnuradio/gnuradio/blob/master/gr-analog/python/analog/fm_emph.py - -import math -import cmath -import numpy as np -import scipy.signal as signal -import pylab as pl - -tau = 750e-6 -fs = 8000 -fh = 2700 - -# Digital corner frequency -w_c = 1.0 / tau - -# Prewarped analog corner frequency -w_ca = 2.0 * fs * math.tan(w_c / (2.0 * fs)) - -# Resulting digital pole, zero, and gain term from the bilinear -# transformation of H(s) = w_ca / (s + w_ca) to -# H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1) -k = -w_ca / (2.0 * fs) -z1 = -1.0 -p1 = (1.0 + k) / (1.0 - k) -b0 = -k / (1.0 - k) - -btaps = [ b0 * 1.0, b0 * -z1, 0 ] -ataps = [ 1.0, -p1, 0 ] - -# Since H(s = 0) = 1.0, then H(z = 1) = 1.0 and has 0 dB gain at DC - - -taps = np.concatenate((btaps, ataps), axis=0) -print("Taps") -print(*taps, "", sep=",", end="\n") - -f,h = signal.freqz(btaps,ataps, fs=fs) -pl.plot(f, 20*np.log10(np.abs(h))) -pl.xlabel('frequency/Hz') -pl.ylabel('gain/dB') -pl.ylim(top=0,bottom=-30) -pl.xlim(left=0, right=fh*2.5) -pl.show() \ No newline at end of file diff --git a/Tools/PreEmphasis.py b/Tools/PreEmphasis.py deleted file mode 100644 index 22c81f6..0000000 --- a/Tools/PreEmphasis.py +++ /dev/null @@ -1,51 +0,0 @@ -#based on https://github.com/gnuradio/gnuradio/blob/master/gr-analog/python/analog/fm_emph.py - -import math -import cmath -import numpy as np -import scipy.signal as signal -import pylab as pl - -tau = 750e-6 -fs = 8000 -fh = 2700 - -# Digital corner frequencies -w_cl = 1.0 / tau -w_ch = 2.0 * math.pi * fh - -# Prewarped analog corner frequencies -w_cla = 2.0 * fs * math.tan(w_cl / (2.0 * fs)) -w_cha = 2.0 * fs * math.tan(w_ch / (2.0 * fs)) - -# Resulting digital pole, zero, and gain term from the bilinear -# transformation of H(s) = (s + w_cla) / (s + w_cha) to -# H(z) = b0 (1 - z1 z^-1)/(1 - p1 z^-1) -kl = -w_cla / (2.0 * fs) -kh = -w_cha / (2.0 * fs) -z1 = (1.0 + kl) / (1.0 - kl) -p1 = (1.0 + kh) / (1.0 - kh) -b0 = (1.0 - kl) / (1.0 - kh) - -# Since H(s = infinity) = 1.0, then H(z = -1) = 1.0 and -# this filter has 0 dB gain at fs/2.0. -# That isn't what users are going to expect, so adjust with a -# gain, g, so that H(z = 1) = 1.0 for 0 dB gain at DC. -w_0dB = 2.0 * math.pi * 0.0 -g = abs(1.0 - p1 * cmath.rect(1.0, -w_0dB)) \ -/ (b0 * abs(1.0 - z1 * cmath.rect(1.0, -w_0dB))) - -btaps = [ g * b0 * 1.0, g * b0 * -z1, 0] -ataps = [ 1.0, -p1, 0] - -taps = np.concatenate((btaps, ataps), axis=0) -print("Taps") -print(*taps, "", sep=",", end="\n") - -f,h = signal.freqz(btaps,ataps, fs=fs) -pl.plot(f, 20*np.log10(np.abs(h))) -pl.xlabel('frequency/Hz') -pl.ylabel('gain/dB') -pl.ylim(top=30,bottom=0) -pl.xlim(left=0, right=fh*2.5) -pl.show() \ No newline at end of file diff --git a/UMP.cpp b/UMP.cpp new file mode 100644 index 0000000..f843322 --- /dev/null +++ b/UMP.cpp @@ -0,0 +1,283 @@ +/* +* Copyright (C) 2016 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 "Defines.h" +#include "Utils.h" +#include "Log.h" +#include "UMP.h" + +#include +#include +#include + +const unsigned char UMP_FRAME_START = 0xF0U; + +const unsigned char UMP_HELLO = 0x00U; + +const unsigned char UMP_SET_MODE = 0x01U; +const unsigned char UMP_SET_TX = 0x02U; +const unsigned char UMP_SET_CD = 0x03U; + +const unsigned char UMP_WRITE_SERIAL = 0x10U; +const unsigned char UMP_READ_SERIAL = 0x11U; + +const unsigned char UMP_STATUS = 0x50U; + +const unsigned int BUFFER_LENGTH = 255U; + +CUMP::CUMP(const std::string& port) : +m_serial(port, SERIAL_115200), +m_open(false), +m_buffer(NULL), +m_length(0U), +m_offset(0U), +m_lockout(false), +m_mode(MODE_IDLE), +m_tx(false), +m_cd(false) +{ + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CUMP::~CUMP() +{ + delete[] m_buffer; +} + +bool CUMP::open() +{ + if (m_open) + return true; + + LogMessage("Opening the UMP"); + + bool ret = m_serial.open(); + if (!ret) + return false; + + unsigned char buffer[3U]; + + buffer[0U] = UMP_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = UMP_HELLO; + + // CUtils::dump(1U, "Transmitted", buffer, 3U); + + int n = m_serial.write(buffer, 3U); + if (n != 3) { + m_serial.close(); + return false; + } + + m_open = true; + + return true; +} + +bool CUMP::setMode(unsigned char mode) +{ + if (mode == m_mode) + return true; + + m_mode = mode; + + unsigned char buffer[4U]; + + buffer[0U] = UMP_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = UMP_SET_MODE; + buffer[3U] = mode; + + // CUtils::dump(1U, "Transmitted", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +bool CUMP::setTX(bool on) +{ + if (on == m_tx) + return true; + + m_tx = on; + + unsigned char buffer[4U]; + + buffer[0U] = UMP_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = UMP_SET_TX; + buffer[3U] = on ? 0x01U : 0x00U; + + // CUtils::dump(1U, "Transmitted", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +bool CUMP::setCD(bool on) +{ + if (on == m_cd) + return true; + + m_cd = on; + + unsigned char buffer[4U]; + + buffer[0U] = UMP_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = UMP_SET_CD; + buffer[3U] = on ? 0x01U : 0x00U; + + // CUtils::dump(1U, "Transmitted", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +bool CUMP::getLockout() const +{ + return m_lockout; +} + +int CUMP::write(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + unsigned char buffer[250U]; + + buffer[0U] = UMP_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = UMP_WRITE_SERIAL; + + ::memcpy(buffer + 3U, data, length); + + // CUtils::dump(1U, "Transmitted", buffer, length + 3U); + + return m_serial.write(buffer, length + 3U); +} + +// To be implemented later if needed +int CUMP::read(unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + return 0; +} + +void CUMP::clock(unsigned int ms) +{ + if (m_offset == 0U) { + // Get the start of the frame or nothing at all + int ret = m_serial.read(m_buffer + 0U, 1U); + if (ret < 0) { + LogError("Error when reading from the UMP"); + return; + } + + if (ret == 0) + return; + + if (m_buffer[0U] != UMP_FRAME_START) + return; + + m_offset = 1U; + } + + if (m_offset == 1U) { + // Get the length of the frame + int ret = m_serial.read(m_buffer + 1U, 1U); + if (ret < 0) { + LogError("Error when reading from the UMP"); + m_offset = 0U; + return; + } + + if (ret == 0) + return; + + if (m_buffer[1U] >= 250U) { + LogError("Invalid length received from the UMP - %u", m_buffer[1U]); + m_offset = 0U; + return; + } + + m_length = m_buffer[1U]; + m_offset = 2U; + } + + if (m_offset == 2U) { + // Get the frame type + int ret = m_serial.read(m_buffer + 2U, 1U); + if (ret < 0) { + LogError("Error when reading from the UMP"); + m_offset = 0U; + return; + } + + if (ret == 0) + return; + + switch (m_buffer[2U]) { + case UMP_STATUS: + case UMP_READ_SERIAL: + break; + + default: + LogError("Unknown message, type: %02X", m_buffer[2U]); + m_offset = 0U; + return; + } + + m_offset = 3U; + } + + if (m_offset >= 3U) { + while (m_offset < m_length) { + int ret = m_serial.read(m_buffer + m_offset, m_length - m_offset); + if (ret < 0) { + LogError("Error when reading from the UMP"); + m_offset = 0U; + return; + } + + if (ret == 0) + return; + + if (ret > 0) + m_offset += ret; + } + } + + m_offset = 0U; + + // CUtils::dump(1U, "Received", m_buffer, m_length); + + if (m_buffer[2U] == UMP_STATUS) + m_lockout = (m_buffer[3U] & 0x01U) == 0x01U; +} + +void CUMP::close() +{ + if (!m_open) + return; + + LogMessage("Closing the UMP"); + + m_serial.close(); + + m_open = false; +} diff --git a/UMP.h b/UMP.h new file mode 100644 index 0000000..1acc3c1 --- /dev/null +++ b/UMP.h @@ -0,0 +1,63 @@ +/* +* Copyright (C) 2016 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(UMP_H) +#define UMP_H + +#include "SerialController.h" +#include "SerialPort.h" + +#include + +class CUMP : public ISerialPort +{ +public: + CUMP(const std::string& port); + virtual ~CUMP(); + + virtual bool open(); + + bool setMode(unsigned char mode); + + bool setTX(bool on); + + bool setCD(bool on); + + bool getLockout() const; + + virtual int read(unsigned char* buffer, unsigned int length); + + virtual int write(const unsigned char* buffer, unsigned int length); + + void clock(unsigned int ms); + + virtual void close(); + +private: + CSerialController m_serial; + bool m_open; + unsigned char* m_buffer; + unsigned int m_length; + unsigned int m_offset; + bool m_lockout; + unsigned char m_mode; + bool m_tx; + bool m_cd; +}; + +#endif diff --git a/UMP/UMP.ino b/UMP/UMP.ino new file mode 100644 index 0000000..4cf98e3 --- /dev/null +++ b/UMP/UMP.ino @@ -0,0 +1,203 @@ +/* +* Copyright (C) 2016,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(__AVR_ATmega1280__) && !defined(__AVR_ATmega2560__) && !defined(__AVR_ATmega32U4__) && !defined(__SAM3X8E__) && !defined(__MK20DX256__) +#include +#endif + +#if !defined(PIN_LED) +#define PIN_LED 13 +#endif + +#define PIN_DSTAR 3 +#define PIN_DMR 4 +#define PIN_YSF 5 +#define PIN_P25 6 +#define PIN_NXDN 7 +#define PIN_POCSAG 8 + +#define PIN_TX 10 +#define PIN_CD 11 + +#define PIN_LOCKOUT 12 + +#if defined(__MK20DX256__) +#define FLASH_DELAY 200000U +#else +#define FLASH_DELAY 3200U +#endif + +#if !defined(__AVR_ATmega1280__) && !defined(__AVR_ATmega2560__) && !defined(__AVR_ATmega32U4__) && !defined(__SAM3X8E__) && !defined(__MK20DX256__) +AltSoftSerial mySerial; +#endif + +// Use the LOCKOUT function on the UMP +// #define USE_LOCKOUT + +void setup() +{ + Serial.begin(115200); + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__) || defined(__SAM3X8E__) || defined(__MK20DX256__) + Serial1.begin(9600); +#else + mySerial.begin(9600); +#endif + + pinMode(PIN_LED, OUTPUT); + pinMode(PIN_DSTAR, OUTPUT); + pinMode(PIN_DMR, OUTPUT); + pinMode(PIN_YSF, OUTPUT); + pinMode(PIN_P25, OUTPUT); + pinMode(PIN_NXDN, OUTPUT); + pinMode(PIN_POCSAG, OUTPUT); + pinMode(PIN_TX, OUTPUT); + pinMode(PIN_CD, OUTPUT); + pinMode(PIN_LOCKOUT, INPUT); + + digitalWrite(PIN_DSTAR, LOW); + digitalWrite(PIN_DMR, LOW); + digitalWrite(PIN_YSF, LOW); + digitalWrite(PIN_P25, LOW); + digitalWrite(PIN_NXDN, LOW); + digitalWrite(PIN_POCSAG, LOW); + digitalWrite(PIN_TX, LOW); + digitalWrite(PIN_CD, LOW); +} + +#define UMP_FRAME_START 0xF0U + +#define UMP_HELLO 0x00U + +#define UMP_SET_MODE 0x01U +#define UMP_SET_TX 0x02U +#define UMP_SET_CD 0x03U + +#define UMP_WRITE_SERIAL 0x10U + +#define UMP_STATUS 0x50U + +#define MODE_IDLE 0U +#define MODE_DSTAR 1U +#define MODE_DMR 2U +#define MODE_YSF 3U +#define MODE_P25 4U +#define MODE_NXDN 5U +#define MODE_POCSAG 6U + +bool m_started = false; +uint32_t m_count = 0U; +bool m_led = false; + +uint8_t m_buffer[256U]; +uint8_t m_offset = 0U; +uint8_t m_length = 0U; + +bool m_lockout = false; + +void loop() +{ + while (Serial.available()) { + uint8_t c = Serial.read(); + + if (m_offset == 0U) { + if (c == UMP_FRAME_START) { + m_buffer[m_offset] = c; + m_offset = 1U; + } + } else if (m_offset == 1U) { + m_length = m_buffer[m_offset] = c; + m_offset = 2U; + } else { + m_buffer[m_offset] = c; + m_offset++; + + if (m_length == m_offset) { + switch (m_buffer[2U]) { + case UMP_HELLO: + m_started = true; + break; + case UMP_SET_MODE: + digitalWrite(PIN_DSTAR, m_buffer[3U] == MODE_DSTAR ? HIGH : LOW); + digitalWrite(PIN_DMR, m_buffer[3U] == MODE_DMR ? HIGH : LOW); + digitalWrite(PIN_YSF, m_buffer[3U] == MODE_YSF ? HIGH : LOW); + digitalWrite(PIN_P25, m_buffer[3U] == MODE_P25 ? HIGH : LOW); + digitalWrite(PIN_NXDN, m_buffer[3U] == MODE_NXDN ? HIGH : LOW); + digitalWrite(PIN_POCSAG, m_buffer[3U] == MODE_POCSAG ? HIGH : LOW); + break; + case UMP_SET_TX: + digitalWrite(PIN_TX, m_buffer[3U] == 0x01U ? HIGH : LOW); + break; + case UMP_SET_CD: + digitalWrite(PIN_CD, m_buffer[3U] == 0x01U ? HIGH : LOW); + break; + case UMP_WRITE_SERIAL: +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__) || defined(__SAM3X8E__) || defined(__MK20DX256__) + Serial1.write(m_buffer + 3U, m_length - 3U); +#else + mySerial.write(m_buffer + 3U, m_length - 3U); +#endif + break; + default: + break; + } + + m_length = 0U; + m_offset = 0U; + } + } + } + + bool lockout = false; +#if defined(USE_LOCKOUT) + lockout = digitalRead(PIN_LOCKOUT) == HIGH; +#endif + if (lockout != m_lockout) { + uint8_t data[4U]; + data[0U] = UMP_FRAME_START; + data[1U] = 4U; + data[2U] = UMP_STATUS; + data[3U] = lockout ? 0x01U : 0x00U; + Serial.write(data, 4U); + + m_lockout = lockout; + } + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega32U4__) || defined(__SAM3X8E__) || defined(__MK20DX256__) + while (Serial1.available()) + Serial1.read(); +#else + while (mySerial.available()) + mySerial.read(); +#endif + + m_count++; + if (m_started) { + if (m_count > FLASH_DELAY) { + digitalWrite(PIN_LED, m_led ? LOW : HIGH); + m_led = !m_led; + m_count = 0U; + } + } else { + if (m_count > (FLASH_DELAY * 3U)) { + digitalWrite(PIN_LED, m_led ? LOW : HIGH); + m_led = !m_led; + m_count = 0U; + } + } +} diff --git a/linux/pi-star/init/README.md b/linux/init/README.md similarity index 100% rename from linux/pi-star/init/README.md rename to linux/init/README.md diff --git a/linux/pi-star/init/mmdvmhost b/linux/init/mmdvmhost similarity index 100% rename from linux/pi-star/init/mmdvmhost rename to linux/init/mmdvmhost diff --git a/linux/pi-star/systemd/mmdvmhost.service b/linux/pi-star/systemd/mmdvmhost.service deleted file mode 100644 index a4de9f3..0000000 --- a/linux/pi-star/systemd/mmdvmhost.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=MMDVMHost Radio Servce -After=syslog.target network.target - -[Service] -Type=forking -ExecStart=/usr/local/sbin/mmdvmhost_service start -ExecStop=/usr/local/sbin/mmdvmhost_service stop -ExecReload=/usr/local/sbin/mmdvmhost_service restart - -[Install] -WantedBy=multi-user.target diff --git a/linux/pi-star/systemd/README.md b/linux/systemd/README.md similarity index 100% rename from linux/pi-star/systemd/README.md rename to linux/systemd/README.md diff --git a/linux/systemd/mmdvmhost.service b/linux/systemd/mmdvmhost.service index 27ca4ee..a4de9f3 100644 --- a/linux/systemd/mmdvmhost.service +++ b/linux/systemd/mmdvmhost.service @@ -1,12 +1,12 @@ [Unit] -Description=MMDVMHost Radio Service +Description=MMDVMHost Radio Servce After=syslog.target network.target [Service] -User=mmdvm Type=forking -ExecStart=/usr/local/bin/MMDVMHost -Restart=on-abnormal +ExecStart=/usr/local/sbin/mmdvmhost_service start +ExecStop=/usr/local/sbin/mmdvmhost_service stop +ExecReload=/usr/local/sbin/mmdvmhost_service restart [Install] WantedBy=multi-user.target diff --git a/linux/pi-star/systemd/mmdvmhost.timer b/linux/systemd/mmdvmhost.timer similarity index 100% rename from linux/pi-star/systemd/mmdvmhost.timer rename to linux/systemd/mmdvmhost.timer diff --git a/linux/pi-star/systemd/mmdvmhost_service b/linux/systemd/mmdvmhost_service similarity index 100% rename from linux/pi-star/systemd/mmdvmhost_service rename to linux/systemd/mmdvmhost_service

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 delta 11735 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 B`lJ8= diff --git a/Nextion_G4KLX/NX3224T028.tft b/Nextion_G4KLX/NX3224T028.tft index 22094938b6e0b3f2d1490526b7557668021ff58c..034441b1c2c901383fe53d2a7f370b43cc9079f2 100644 GIT binary patch delta 5011 zcmd7W`*#%e9merzF4<&ZDS_;6KrRk}aEqXz6an$D8>N*NED9(HD5$911O!Srg_VnN z5Do;8jEKVFRtg$h1VoA+jf#qbqIlz`P(h@{o~S6rV(oiAwtqo?shZd4ndiH^v&qix zjC){R>)Jythf7`hEo+dkiwC7%POWOuk+k?!{(qd?Pny6jn!hjgxVk)F5lo+TLkE)rU;)*wCuu-~RMda(X~jc=Y6ZVS_2LIlh`5pAmN2 z`i3F(&PQS2!f5xn^+f9sk*y3i!k}y7i(L4Mq8l%HQ0~ssBnn8?9!_Bua zHik=YWt4>#gBd%*5knYT!W(X5yd8EL8b9s+P$#@(Sp0Kme&`IREeg*>V^jFVhp{u@@dC|C6e@sL#6u0DkO)~cbRJVmxKUFNeKoM$5M3oLr?9!M#L3!( zF;!zyHI`q;gbrFh1$=yR3tr>S0PH*0e-lZ7jD%SM4q5${3zHV!0+_pry93%=#5r ztf#E0MqgVkKZCBeTi$`5p2MuZ$3T~G3Ubs0JvGO)uCk^J`s!x6JG#0iXB$1Sz3#yc zs#_2p0Sz#zQp;tSP*=-6F(XcU{ASBB&^GILS^vHD_2W^0ImXllGn$GiEwuhg>$hUL z&c!+Yi&eKybcZsGsS{@P0H(FT`pxKQ3wo+Xj$~_Iwq}pz*U;CSmfu2G`z^nXp5DcT zj#=}GH7Br0=jw3$JNlLlUl(lodvtZt@(;)XUpM*UyZK1Tke6LuElcA!a!@SUuXSBEY|brEBBHu)Y!%z%derUH!SZ(Py4W3^?w#U z(BSx|$4njw_Sia5jP;@oi^6ltpBxbZe#|ZR_Exc+AuVYF_(bsY7 zPgp)>`Ezvjh2=Bo=`3c|k*kD2{m{{k=;t6IV)|_BYHm9MZiuxS?zGm3QOyrDY zc@|!y7x1W77Dn%jHJH>k%eye4J(l0Xj1mo^C*1-AWiX~+U{(t-tu@xaX#F1R^BYEe zvxXf1fqJqLQ#oe!kS(mjK)bE4!E*UU(Vw~r2I_$^Ww2axtzTvRZtH6>BbN*1l!`G> zSB$AAW|hOVR#?9SbB>;6c-nBIjvq<|F56%ZrfHwB;qpX&O0ABd2NW z&sl#SIZdad`xc_329{Hn)99*^Qir>2q{-Sv)n$%aCMn&bU;s6U{;eb&@$_nTVI7~J!MTb zaw@X?400;6yaU_oQ{13AjiOh50Vef?<@K1*A1&{|jJ|8c@t;!N#?j8okk5b2XcDG0 z-}+6~S6lyg>%B{&+qcA++F`lIVzK5}zux-InAP7fEiUu9eG7C{f}Tn-scuc|^S`@Q zJ#G0~^p&+-j;^k^+#9*V!Gs1{Gsv33nAA9H#-p!EmLEV@Q!GzKPZgNapE1y3>yIFx z|DP~Y`o^kr=<6SrFQBV`TE2*$e#DG=HH{wP4s7*VRai3}IU!ljp{rSzXQQWw zuv~9opf936r!%&27K`OIiyqWRUj>%q=&G*e1bV8ES@p+26Rn?Q{S-`Vp*4%pSEc2p z=xVv;N3fNi!%F?KdGzw-`Z1CkX&d)qLX#~|$BcGkN(V4dt@Yno-}Ta{|1}00i!n{Y za_zuk?X&)@^w|$tqoXnCX?%{6)NHHfST)b`eB^y^c@gryx4Z;--y`pP zVO%I$CTz+zux-ISgy0kAzT{Wz9sV6fLV>fv}Req*7{AD z&^F}w-_GF61Wf9!sM0=b-m!cDeZ6n_19bI~<&V+R5zMG*>u8tPprfAXsTZa+(3(N$ zYl!9B(A6-@!_iZ21S6w$80ckNc*Pc8!<3F#a}>E4v3vqueP;P2dOD2>ePhizYtCbl z>a>ZTP+jy@-*O>xEor$38>uYEsMKzZsRon!!158~H)|U`iB1@(KgM(mX7s4_)zP%rwDrM4jYdSya>^u@}A{OJGGC;0x4VR~u&=l;W` HV{-onB7g3` delta 15069 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 P@BGW1t$$T(O8S2T3T#;Z diff --git a/Nextion_G4KLX/NX4024K032.HMI b/Nextion_G4KLX/NX4024K032.HMI index 85cdc4811bc8f1c6dc4cd7ead429465df4b0d4c8..7df6343eacb3e26ab4d3451f832b73b4b004da7e 100644 GIT binary patch delta 239 zcmZ2?Ky~I(r3n(whK(Ey3=9wwLG!O%ctiv!4goV4abT!G(lI^o411=Fd;8j!mjXvz zUvD|=`uNOY*E+$MKz!77yU-C=j|pd77XWd~>@%(mo6or3nRUjsO7MtlB@~0?L2Qso z^%DzNS(v?6yxTmH@j6R$I$L`>8zT@i0WmWWvj8zG5VLJhXJgl%zdh5FWB%>waaNow hx64~|DsxV+a^+mH-O7#gir93%1Wp;26Auq@0|07+PBQ=i delta 11465 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 diff --git a/Nextion_G4KLX/NX4024K032.tft b/Nextion_G4KLX/NX4024K032.tft index 04da59ad9f8ca248a86b1eab7907c6ea0a0d9f78..92b2843e9b7fadf316406772aa42346e99aae699 100644 GIT binary patch delta 5141 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 delta 15319 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 diff --git a/Nextion_G4KLX/NX4024T032.HMI b/Nextion_G4KLX/NX4024T032.HMI index 10dbda426d79c22dc1ead57b2a3361c9b50e36b6..03cd4a81fe4eff2c588f9ab3c31d1349bf8646c3 100644 GIT binary patch delta 237 zcmZ2?Ky~I(r3n(whK(Ey3=9wwL9g`n%oPEOL%_^M92hE)bWAsV$eQW$DmeHQPy0k` zKILiFY|+!KXSeME;tAG4trM-+vd^@31>!`WnbuELW?EbF&a|H2Hqm+x6oceJ?CFXp z*qd1yT>M|zZ=T3_ouxUQtv#KM5r~<9m>Gy!fS47C*|w*%v1`xYp6SUk|Mv7aE6$bM f<*hlDIj2{-a<15J<;HnMY&u^8rwq%!@P%IhI?+br delta 11805 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 F{{T{G6e<7! delta 15298 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