/* * Copyright (C) 2011-2014 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 "DVRPTRV3Controller.h" #include "DStarDefines.h" #include "Logger.h" #include "Timer.h" #include #include #include #include #if !defined(_WIN32) #include #include #endif const unsigned int MAX_RESPONSES = 30U; const unsigned int BUFFER_LENGTH = 200U; CDVRPTRV3Controller::CDVRPTRV3Controller(const std::string& port, const std::string& path, bool txInvert, unsigned int modLevel, bool duplex, const std::string& callsign, unsigned int txDelay) : CModem(), m_connection(CT_USB), m_usbPort(port), m_usbPath(path), m_address(), m_port(0U), m_txInvert(txInvert), m_modLevel(modLevel), m_duplex(duplex), m_callsign(callsign), m_txDelay(txDelay), m_usb(nullptr), m_network(nullptr), m_buffer(nullptr), m_txData(1000U), m_rx(false) { assert(!port.empty()); m_usb = new CSerialDataController(port, SERIAL_115200); m_buffer = new unsigned char[BUFFER_LENGTH]; } CDVRPTRV3Controller::CDVRPTRV3Controller(const std::string& address, unsigned int port, bool txInvert, unsigned int modLevel, bool duplex, const std::string& callsign, unsigned int txDelay) : CModem(), m_connection(CT_NETWORK), m_usbPort(), m_usbPath(), m_address(address), m_port(port), m_txInvert(txInvert), m_modLevel(modLevel), m_duplex(duplex), m_callsign(callsign), m_txDelay(txDelay), m_usb(nullptr), m_network(nullptr), m_buffer(nullptr), m_txData(1000U), m_rx(false) { assert(!address.empty()); assert(port > 0U); m_network = new CTCPReaderWriter(address, port); m_buffer = new unsigned char[BUFFER_LENGTH]; } CDVRPTRV3Controller::~CDVRPTRV3Controller() { delete m_usb; delete m_network; delete[] m_buffer; } bool CDVRPTRV3Controller::start() { findPort(); bool ret = openModem(); if (!ret) return false; findPath(); m_thread = std::thread(&CDVRPTRV3Controller::entry, this); return true; } void CDVRPTRV3Controller::entry() { wxLogMessage("Starting DV-RPTR3 Modem Controller thread"); // Clock every 5ms-ish CTimer pollTimer(200U, 0U, 250U); pollTimer.start(); unsigned int space = 0U; while (!m_stopped) { // Poll the modem status every 250ms if (pollTimer.hasExpired()) { bool ret = readSpace(); if (!ret) { ret = findModem(); if (!ret) { wxLogMessage("Stopping DV-RPTR3 Modem Controller thread"); return; } } pollTimer.start(); } unsigned int length; RESP_TYPE_V3 type = getResponse(m_buffer, length); switch (type) { case RT3_TIMEOUT: break; case RT3_ERROR: { bool ret = findModem(); if (!ret) { wxLogMessage("Stopping DV-RPTR3 Modem Controller thread"); return; } } break; case RT3_HEADER: { // CUtils::dump("RT3_HEADER", m_buffer, length); std::lock_guard lock(m_mutex); unsigned char data[2U]; data[0U] = DSMTT_HEADER; data[1U] = RADIO_HEADER_LENGTH_BYTES; m_rxData.addData(data, 2U); m_rxData.addData(m_buffer + 9U, RADIO_HEADER_LENGTH_BYTES - 2U); // Dummy checksum data[0U] = 0xFFU; data[1U] = 0xFFU; m_rxData.addData(data, 2U); data[0U] = DSMTT_DATA; data[1U] = DV_FRAME_LENGTH_BYTES; m_rxData.addData(data, 2U); m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES); m_rx = true; } break; case RT3_DATA: { // CUtils::dump("RT3_DATA", m_buffer, length); std::lock_guard lock(m_mutex); unsigned char data[2U]; data[0U] = DSMTT_DATA; data[1U] = DV_FRAME_LENGTH_BYTES; m_rxData.addData(data, 2U); m_rxData.addData(m_buffer + 5U, DV_FRAME_LENGTH_BYTES); m_rx = true; // End of transmission? bool end = (m_buffer[19U] & 0x40U) == 0x40U; if (end) { data[0U] = DSMTT_EOT; data[1U] = 0U; m_rxData.addData(data, 2U); m_rx = false; } } break; case RT3_SPACE: space = m_buffer[9U]; // CUtils::dump("RT3_SPACE", m_buffer, length); break; // These should not be received in this loop, but don't complain if we do case RT3_QUERY: case RT3_CONFIG: break; default: wxLogMessage("Unknown DV-RPTR3 message, type"); CUtils::dump("Buffer dump", m_buffer, length); break; } if (space >= 4U) { if (m_txData.hasData()) { unsigned char len = 0U; unsigned char data[200U]; { std::lock_guard lock(m_mutex); m_txData.getData(&len, 1U); m_txData.getData(data, len); } // CUtils::dump("Write", data, len); bool ret = writeModem(data, len); if (!ret) { ret = findModem(); if (!ret) { wxLogMessage("Stopping DV-RPTR3 Modem Controller thread"); return; } } else { if (len > 100U) space -= 4U; else space--; } } } std::this_thread::sleep_for(std::chrono::milliseconds(5)); pollTimer.clock(); } wxLogMessage("Stopping DV-RPTR3 Modem Controller thread"); closeModem(); } bool CDVRPTRV3Controller::writeHeader(const CHeaderData& header) { bool ret = m_txData.hasSpace(106U); if (!ret) { wxLogWarning("No space to write the header"); return false; } unsigned char buffer[105U]; ::memset(buffer, 0x00U, 105U); buffer[0U] = 'H'; buffer[1U] = 'E'; buffer[2U] = 'A'; buffer[3U] = 'D'; buffer[4U] = 'X'; buffer[5U] = '0'; buffer[6U] = '0'; buffer[7U] = '0'; buffer[8U] = '2'; ::memset(buffer + 9U, ' ', RADIO_HEADER_LENGTH_BYTES); buffer[9U] = header.getFlag1(); buffer[10U] = header.getFlag2(); buffer[11U] = header.getFlag3(); std::string rpt2 = header.getRptCall2(); for (unsigned int i = 0U; i < rpt2.size() && i < LONG_CALLSIGN_LENGTH; i++) buffer[i + 12U] = rpt2[i]; std::string rpt1 = header.getRptCall1(); for (unsigned int i = 0U; i < rpt1.size() && i < LONG_CALLSIGN_LENGTH; i++) buffer[i + 20U] = rpt1[i]; std::string your = header.getYourCall(); for (unsigned int i = 0U; i < your.size() && i < LONG_CALLSIGN_LENGTH; i++) buffer[i + 28U] = your[i]; std::string my1 = header.getMyCall1(); for (unsigned int i = 0U; i < my1.size() && i < LONG_CALLSIGN_LENGTH; i++) buffer[i + 36U] = my1[i]; std::string my2 = header.getMyCall2(); for (unsigned int i = 0U; i < my2.size() && i < SHORT_CALLSIGN_LENGTH; i++) buffer[i + 44U] = my2[i]; std::lock_guard lock(m_mutex); unsigned char len = 105U; m_txData.addData(&len, 1U); m_txData.addData(buffer, 105U); m_tx = true; return true; } bool CDVRPTRV3Controller::writeData(const unsigned char* data, unsigned int, bool end) { bool ret = m_txData.hasSpace(18U); if (!ret) { wxLogWarning("No space to write data"); return false; } unsigned char buffer[17U]; ::memset(buffer, 0x00U, 17U); buffer[0U] = 'H'; buffer[1U] = 'E'; buffer[2U] = 'A'; buffer[3U] = 'D'; buffer[4U] = 'Z'; ::memcpy(buffer + 5U, data, DV_FRAME_LENGTH_BYTES); if (end) { buffer[14U] = 0x55U; buffer[15U] = 0x55U; buffer[16U] = 0x55U; m_tx = false; } std::lock_guard lock(m_mutex); unsigned char len = 17U; m_txData.addData(&len, 1U); m_txData.addData(buffer, 17U); return true; } unsigned int CDVRPTRV3Controller::getSpace() { return m_txData.freeSpace() / 18U; } bool CDVRPTRV3Controller::isTXReady() { return m_txData.isEmpty(); } bool CDVRPTRV3Controller::readSerial() { unsigned char buffer[105U]; ::memset(buffer, 0x00U, 105U); buffer[0U] = 'H'; buffer[1U] = 'E'; buffer[2U] = 'A'; buffer[3U] = 'D'; buffer[4U] = 'X'; buffer[5U] = '9'; buffer[6U] = '0'; buffer[7U] = '0'; buffer[8U] = '0'; // CUtils::dump("Written", buffer, 105U); bool ret = writeModem(buffer, 105U); if (!ret) return false; unsigned int count = 0U; unsigned int length; RESP_TYPE_V3 resp; do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); resp = getResponse(m_buffer, length); if (resp != RT3_QUERY) { count++; if (count >= MAX_RESPONSES) { wxLogError("The DV-RPTR modem is not responding to the query command"); return false; } } } while (resp != RT3_QUERY); wxLogInfo("DV-RPTR Modem Hardware serial: 0x%02X%02X%02x%02X", m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U]); return true; } bool CDVRPTRV3Controller::readSpace() { unsigned char buffer[10U]; buffer[0U] = 'H'; buffer[1U] = 'E'; buffer[2U] = 'A'; buffer[3U] = 'D'; buffer[4U] = 'Y'; buffer[5U] = '9'; buffer[6U] = '0'; buffer[7U] = '1'; buffer[8U] = '1'; buffer[9U] = 0x00U; // CUtils::dump("Written", buffer, 10U); return writeModem(buffer, 10U); } bool CDVRPTRV3Controller::setConfig() { unsigned char buffer[105U]; ::memset(buffer, 0x00U, 105U); buffer[0U] = 'H'; buffer[1U] = 'E'; buffer[2U] = 'A'; buffer[3U] = 'D'; buffer[4U] = 'X'; buffer[5U] = '9'; buffer[6U] = '0'; buffer[7U] = '0'; buffer[8U] = '1'; ::memset(buffer + 9U, ' ', LONG_CALLSIGN_LENGTH); for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH && i < m_callsign.size(); i++) buffer[9U + i] = m_callsign[i]; buffer[65U] = m_duplex ? 0x03U : 0x00U; buffer[66U] = m_txInvert ? 0x01U : 0x00U; buffer[73U] = (m_modLevel * 256U) / 100U; // Internal and external speaker on buffer[86U] = 0x03U; buffer[87U] = 0x01U; if (m_txDelay < 100U) m_txDelay = 100U; if (m_txDelay > 850U) m_txDelay = 850U; buffer[89U] = m_txDelay / 10U; // CUtils::dump("Written", buffer, 105U); bool ret = writeModem(buffer, 105U); if (!ret) return false; unsigned int count = 0U; unsigned int length; RESP_TYPE_V3 resp; do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); resp = getResponse(m_buffer, length); if (resp != RT3_CONFIG) { count++; if (count >= MAX_RESPONSES) { wxLogError("The DV-RPTR modem is not responding to the configure command"); return false; } } } while (resp != RT3_CONFIG); // CUtils::dump("Response", m_buffer, length); std::string firmware((char*)(m_buffer + 9U)); wxLogInfo("DV-RPTR Modem Firmware version: %s", firmware.c_str()); return true; } // Reads one "HEAD" protocol frame. See CDVRPTRV2Controller::getResponse() // for full protocol documentation; V3 uses an identical frame structure. RESP_TYPE_V3 CDVRPTRV3Controller::getResponse(unsigned char *buffer, unsigned int& length) { // Get the start of the frame or nothing at all int ret = readModem(buffer + 0U, 5U); if (ret < 0) { wxLogError("Error when reading from the DV-RPTR"); return RT3_ERROR; } if (ret == 0) return RT3_TIMEOUT; while (::memcmp(buffer + 0U, "HEAD", 4U) != 0) { buffer[0U] = buffer[1U]; buffer[1U] = buffer[2U]; buffer[2U] = buffer[3U]; buffer[3U] = buffer[4U]; ret = readModem(buffer + 4U, 1U); if (ret < 0) { wxLogError("Error when reading from the DV-RPTR"); return RT3_ERROR; } if (ret == 0) return RT3_TIMEOUT; } switch (buffer[4U]) { case 'X': length = 105U; break; case 'Y': length = 10U; break; case 'Z': length = 20U; break; default: wxLogError("DV-RPTR frame type is incorrect - 0x%02X", buffer[4U]); return RT3_UNKNOWN; } unsigned int offset = 5U; while (offset < length) { ret = readModem(buffer + offset, length - offset); if (ret < 0) { wxLogError("Error when reading from the DV-RPTR"); return RT3_ERROR; } if (ret > 0) offset += ret; if (ret == 0) std::this_thread::sleep_for(std::chrono::milliseconds(5)); } // CUtils::dump("Received", buffer, length); if (::memcmp(buffer + 0U, "HEADZ", 5U) == 0) { return RT3_DATA; } else if (::memcmp(buffer + 5U, "0001", 4U) == 0) { if (buffer[104U] == 0x01U) return RT3_HEADER; } else if (::memcmp(buffer + 5U, "9900", 4U) == 0) { return RT3_QUERY; } else if (::memcmp(buffer + 5U, "9001", 4U) == 0) { return RT3_CONFIG; } else if (::memcmp(buffer + 5U, "9011", 4U) == 0) { return RT3_SPACE; } else if (::memcmp(buffer + 5U, "9021", 4U) == 0) { return RT3_TIMEOUT; } return RT3_UNKNOWN; } std::string CDVRPTRV3Controller::getPath() const { return m_usbPath; } bool CDVRPTRV3Controller::findPort() { #if !defined(_WIN32) if (m_connection != CT_USB) return true; if (m_usbPath.empty()) return false; DIR* dir = ::opendir("/sys/class/tty"); if (dir == nullptr) { wxLogError("Cannot open directory /sys/class/tty"); return false; } struct dirent* entry; while ((entry = ::readdir(dir)) != nullptr) { std::string fileName(entry->d_name); // Match ttyACM* entries if (fileName.substr(0, 6) != "ttyACM") continue; std::string path = "/sys/class/tty/" + fileName; char cpath[255U]; ::strncpy(cpath, path.c_str(), sizeof(cpath) - 1); cpath[sizeof(cpath) - 1] = '\0'; char symlink[255U]; int ret2 = ::readlink(cpath, symlink, sizeof(symlink) - 1); if (ret2 < 0) { ::strncat(cpath, "/device", sizeof(cpath) - ::strlen(cpath) - 1); ret2 = ::readlink(cpath, symlink, sizeof(symlink) - 1); if (ret2 < 0) { wxLogError("Error from readlink()"); ::closedir(dir); return false; } symlink[ret2] = '\0'; path = std::string(symlink, ret2); } else { symlink[ret2] = '\0'; std::string fullPath(symlink, ret2); size_t pos = fullPath.rfind('/'); path = (pos != std::string::npos) ? fullPath.substr(0, pos) : fullPath; } if (path == m_usbPath) { m_usbPort = "/dev/" + fileName; wxLogMessage("Found modem port of %s based on the path", m_usbPort.c_str()); ::closedir(dir); return true; } } ::closedir(dir); return false; #else return true; #endif } bool CDVRPTRV3Controller::findPath() { #if !defined(_WIN32) if (m_connection != CT_USB) return true; std::string path = "/sys/class/tty/" + m_usbPort.substr(5U); char cpath[255U]; ::strncpy(cpath, path.c_str(), sizeof(cpath) - 1); cpath[sizeof(cpath) - 1] = '\0'; char symlink[255U]; int ret = ::readlink(cpath, symlink, sizeof(symlink) - 1); if (ret < 0) { ::strncat(cpath, "/device", sizeof(cpath) - ::strlen(cpath) - 1); ret = ::readlink(cpath, symlink, sizeof(symlink) - 1); if (ret < 0) { wxLogError("Error from readlink()"); return false; } symlink[ret] = '\0'; path = std::string(symlink, ret); } else { symlink[ret] = '\0'; std::string fullPath(symlink, ret); size_t pos = fullPath.rfind('/'); path = (pos != std::string::npos) ? fullPath.substr(0, pos) : fullPath; } if (m_usbPath.empty()) wxLogMessage("Found modem path of %s", path.c_str()); m_usbPath = path; return true; #else return true; #endif } bool CDVRPTRV3Controller::findModem() { closeModem(); // Tell the repeater that the signal has gone away if (m_rx) { std::lock_guard lock(m_mutex); unsigned char data[2U]; data[0U] = DSMTT_EOT; data[1U] = 0U; m_rxData.addData(data, 2U); m_rx = false; } unsigned int count = 0U; // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s while (!m_stopped) { count++; if (count >= 4U) { wxLogMessage("Trying to reopen the modem"); bool ret = findPort(); if (ret) { ret = openModem(); if (ret) return true; } count = 0U; } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } return false; } bool CDVRPTRV3Controller::openModem() { bool ret = false; switch (m_connection) { case CT_USB: ret = m_usb->open(); break; case CT_NETWORK: ret = m_network->open(); break; default: wxLogError("Invalid connection type: %d", int(m_connection)); break; } if (!ret) return false; ret = readSerial(); if (!ret) { closeModem(); return false; } ret = setConfig(); if (!ret) { closeModem(); return false; } return true; } int CDVRPTRV3Controller::readModem(unsigned char* buffer, unsigned int length) { switch (m_connection) { case CT_USB: return m_usb->read(buffer, length); case CT_NETWORK: return m_network->read(buffer, length, 0U); default: return -1; } } bool CDVRPTRV3Controller::writeModem(const unsigned char* buffer, unsigned int length) { switch (m_connection) { case CT_USB: return m_usb->write(buffer, length) == int(length); case CT_NETWORK: return m_network->write(buffer, length); default: return false; } } void CDVRPTRV3Controller::closeModem() { switch (m_connection) { case CT_USB: return m_usb->close(); case CT_NETWORK: return m_network->close(); default: return; } }