You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1123 lines
24 KiB
1123 lines
24 KiB
/*
|
|
* Copyright (C) 2011-2015 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 "CCITTChecksumReverse.h"
|
|
#include "DVMegaController.h"
|
|
#include "CCITTChecksum.h"
|
|
#include "DStarDefines.h"
|
|
#include "Logger.h"
|
|
#include "Timer.h"
|
|
#include "Utils.h"
|
|
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include "EndianCompat.h"
|
|
#if !defined(_WIN32)
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
const unsigned char DVRPTR_HEADER_LENGTH = 5U;
|
|
|
|
const unsigned char DVRPTR_FRAME_START = 0xD0U;
|
|
|
|
const unsigned char DVRPTR_GET_STATUS = 0x10U;
|
|
const unsigned char DVRPTR_GET_VERSION = 0x11U;
|
|
const unsigned char DVRPTR_GET_SERIAL = 0x12U;
|
|
const unsigned char DVRPTR_GET_CONFIG = 0x13U;
|
|
const unsigned char DVRPTR_SET_CONFIG = 0x14U;
|
|
const unsigned char DVRPTR_RXPREAMBLE = 0x15U;
|
|
const unsigned char DVRPTR_START = 0x16U;
|
|
const unsigned char DVRPTR_HEADER = 0x17U;
|
|
const unsigned char DVRPTR_RXSYNC = 0x18U;
|
|
const unsigned char DVRPTR_DATA = 0x19U;
|
|
const unsigned char DVRPTR_EOT = 0x1AU;
|
|
const unsigned char DVRPTR_RXLOST = 0x1BU;
|
|
const unsigned char DVRPTR_MSG_RSVD1 = 0x1CU;
|
|
const unsigned char DVRPTR_MSG_RSVD2 = 0x1DU;
|
|
const unsigned char DVRPTR_MSG_RSVD3 = 0x1EU;
|
|
const unsigned char DVRPTR_SET_TESTMDE = 0x1FU;
|
|
|
|
const unsigned char DVRPTR_ACK = 0x06U;
|
|
const unsigned char DVRPTR_NAK = 0x15U;
|
|
|
|
const unsigned int MAX_RESPONSES = 30U;
|
|
|
|
const unsigned int BUFFER_LENGTH = 200U;
|
|
|
|
CDVMegaController::CDVMegaController(const std::string& port, const std::string& path, bool rxInvert, bool txInvert, unsigned int txDelay) :
|
|
CModem(),
|
|
m_port(port),
|
|
m_path(path),
|
|
m_rxInvert(rxInvert),
|
|
m_txInvert(txInvert),
|
|
m_txDelay(txDelay),
|
|
m_rxFrequency(0U),
|
|
m_txFrequency(0U),
|
|
m_power(0U),
|
|
m_serial(port, SERIAL_115200, true),
|
|
m_buffer(nullptr),
|
|
m_txData(1000U),
|
|
m_txCounter(0U),
|
|
m_pktCounter(0U),
|
|
m_rx(false),
|
|
m_txSpace(0U),
|
|
m_txEnabled(false),
|
|
m_checksum(false)
|
|
{
|
|
assert(!port.empty());
|
|
|
|
m_buffer = new unsigned char[BUFFER_LENGTH];
|
|
}
|
|
|
|
CDVMegaController::CDVMegaController(const std::string& port, const std::string& path, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power) :
|
|
CModem(),
|
|
m_port(port),
|
|
m_path(path),
|
|
m_rxInvert(false),
|
|
m_txInvert(false),
|
|
m_txDelay(txDelay),
|
|
m_rxFrequency(rxFrequency),
|
|
m_txFrequency(txFrequency),
|
|
m_power(power),
|
|
m_serial(port, SERIAL_115200, true),
|
|
m_buffer(nullptr),
|
|
m_txData(1000U),
|
|
m_txCounter(0U),
|
|
m_pktCounter(0U),
|
|
m_rx(false),
|
|
m_txSpace(0U),
|
|
m_txEnabled(false),
|
|
m_checksum(false)
|
|
{
|
|
assert(!port.empty());
|
|
assert((rxFrequency >= 144000000U && rxFrequency <= 148000000U) ||
|
|
(rxFrequency >= 420000000U && rxFrequency <= 450000000U));
|
|
assert((txFrequency >= 144000000U && txFrequency <= 148000000U) ||
|
|
(txFrequency >= 420000000U && txFrequency <= 450000000U));
|
|
|
|
m_buffer = new unsigned char[BUFFER_LENGTH];
|
|
}
|
|
|
|
CDVMegaController::~CDVMegaController()
|
|
{
|
|
delete[] m_buffer;
|
|
}
|
|
|
|
bool CDVMegaController::start()
|
|
{
|
|
findPort();
|
|
|
|
bool ret = openModem();
|
|
if (!ret)
|
|
return false;
|
|
|
|
findPath();
|
|
|
|
m_thread = std::thread(&CDVMegaController::entry, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CDVMegaController::entry()
|
|
{
|
|
wxLogMessage("Starting DVMEGA Controller thread");
|
|
|
|
// Clock every 5ms-ish
|
|
CTimer pollTimer(200U, 0U, 100U);
|
|
pollTimer.start();
|
|
|
|
unsigned char writeType = DSMTT_NONE;
|
|
unsigned char writeLength = 0U;
|
|
unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH];
|
|
|
|
unsigned int space = 0U;
|
|
|
|
while (!m_stopped) {
|
|
// Poll the modem status every 100ms
|
|
if (pollTimer.hasExpired()) {
|
|
bool ret = readStatus();
|
|
if (!ret) {
|
|
ret = findModem();
|
|
if (!ret) {
|
|
wxLogMessage("Stopping DVMEGA Controller thread");
|
|
delete[] writeBuffer;
|
|
return;
|
|
}
|
|
}
|
|
|
|
pollTimer.start();
|
|
}
|
|
|
|
unsigned int length;
|
|
RESP_TYPE_MEGA type = getResponse(m_buffer, length);
|
|
|
|
switch (type) {
|
|
case RTM_TIMEOUT:
|
|
break;
|
|
|
|
case RTM_ERROR: {
|
|
bool ret = findModem();
|
|
if (!ret) {
|
|
wxLogMessage("Stopping DVMEGA Controller thread");
|
|
delete[] writeBuffer;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RTM_RXPREAMBLE:
|
|
// wxLogMessage("RT_PREAMBLE");
|
|
break;
|
|
|
|
case RTM_START:
|
|
// wxLogMessage("RT_START");
|
|
break;
|
|
|
|
case RTM_HEADER:
|
|
CUtils::dump("RT_HEADER", m_buffer, length);
|
|
if (length == 7U) {
|
|
if (m_buffer[4U] == DVRPTR_NAK)
|
|
wxLogWarning("Received a header NAK from the DVMEGA");
|
|
} else {
|
|
bool correct = (m_buffer[5U] & 0x80U) == 0x00U;
|
|
if (correct) {
|
|
std::lock_guard<std::mutex> 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 + 8U, RADIO_HEADER_LENGTH_BYTES);
|
|
|
|
m_rx = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RTM_RXSYNC:
|
|
// wxLogMessage("RT_RXSYNC");
|
|
break;
|
|
|
|
case RTM_DATA:
|
|
CUtils::dump("RT_DATA", m_buffer, length);
|
|
if (length == 7U) {
|
|
if (m_buffer[4U] == DVRPTR_NAK)
|
|
wxLogWarning("Received a data NAK from the DVMEGA");
|
|
} else {
|
|
std::lock_guard<std::mutex> 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 + 8U, DV_FRAME_LENGTH_BYTES);
|
|
|
|
m_rx = true;
|
|
}
|
|
break;
|
|
|
|
case RTM_EOT: {
|
|
// wxLogMessage("RT_EOT");
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
unsigned char data[2U];
|
|
data[0U] = DSMTT_EOT;
|
|
data[1U] = 0U;
|
|
m_rxData.addData(data, 2U);
|
|
|
|
m_rx = false;
|
|
}
|
|
break;
|
|
|
|
case RTM_RXLOST: {
|
|
// wxLogMessage("RT_LOST");
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
unsigned char data[2U];
|
|
data[0U] = DSMTT_LOST;
|
|
data[1U] = 0U;
|
|
m_rxData.addData(data, 2U);
|
|
|
|
m_rx = false;
|
|
}
|
|
break;
|
|
|
|
case RTM_GET_STATUS: {
|
|
m_txEnabled = (m_buffer[4U] & 0x02U) == 0x02U;
|
|
m_checksum = (m_buffer[4U] & 0x08U) == 0x08U;
|
|
m_tx = (m_buffer[5U] & 0x02U) == 0x02U;
|
|
m_txSpace = m_buffer[8U];
|
|
space = m_txSpace - m_buffer[9U];
|
|
// CUtils::dump("GET_STATUS", m_buffer, length);
|
|
// wxLogMessage("PTT=%d tx=%u space=%u cksum=%d, tx enabled=%d", int(m_tx), m_txSpace, space, int(m_checksum), int(m_txEnabled));
|
|
}
|
|
break;
|
|
|
|
// These should not be received in this loop, but don't complain if we do
|
|
case RTM_GET_VERSION:
|
|
case RTM_GET_SERIAL:
|
|
case RTM_GET_CONFIG:
|
|
break;
|
|
|
|
default:
|
|
wxLogMessage("Unknown message, type: %02X", m_buffer[3U]);
|
|
CUtils::dump("Buffer dump", m_buffer, length);
|
|
break;
|
|
}
|
|
|
|
if (space > 0U) {
|
|
if (writeType == DSMTT_NONE && m_txData.hasData()) {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
m_txData.getData(&writeType, 1U);
|
|
m_txData.getData(&writeLength, 1U);
|
|
m_txData.getData(writeBuffer, writeLength);
|
|
}
|
|
|
|
// Only send the start when the TX is off
|
|
if (!m_tx && writeType == DSMTT_START) {
|
|
// CUtils::dump("Write Header", writeBuffer, writeLength);
|
|
|
|
int ret = m_serial.write(writeBuffer, writeLength);
|
|
if (ret != int(writeLength))
|
|
wxLogWarning("Error when writing the header to the DVMEGA");
|
|
|
|
writeType = DSMTT_NONE;
|
|
space--;
|
|
}
|
|
|
|
if (space > 4U && writeType == DSMTT_HEADER) {
|
|
// CUtils::dump("Write Header", writeBuffer, writeLength);
|
|
|
|
int ret = m_serial.write(writeBuffer, writeLength);
|
|
if (ret != int(writeLength))
|
|
wxLogWarning("Error when writing the header to the DVMEGA");
|
|
|
|
writeType = DSMTT_NONE;
|
|
space -= 4U;
|
|
}
|
|
|
|
if (writeType == DSMTT_DATA || writeType == DSMTT_EOT) {
|
|
// CUtils::dump("Write Data", writeBuffer, writeLength);
|
|
|
|
int ret = m_serial.write(writeBuffer, writeLength);
|
|
if (ret != int(writeLength))
|
|
wxLogWarning("Error when writing data to the DVMEGA");
|
|
|
|
writeType = DSMTT_NONE;
|
|
space--;
|
|
}
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
|
pollTimer.clock();
|
|
}
|
|
|
|
wxLogMessage("Stopping DVMEGA Controller thread");
|
|
|
|
setEnabled(false);
|
|
|
|
delete[] writeBuffer;
|
|
|
|
m_serial.close();
|
|
}
|
|
|
|
bool CDVMegaController::writeHeader(const CHeaderData& header)
|
|
{
|
|
if (!m_txEnabled)
|
|
return false;
|
|
|
|
bool ret = m_txData.hasSpace(64U);
|
|
if (!ret) {
|
|
wxLogWarning("No space to write the header");
|
|
return false;
|
|
}
|
|
|
|
m_txCounter++;
|
|
if (m_txCounter == 0U)
|
|
m_txCounter = 1U;
|
|
|
|
unsigned char buffer1[10U];
|
|
|
|
buffer1[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer1[1U] = 0x03U;
|
|
buffer1[2U] = 0x00U;
|
|
|
|
buffer1[3U] = DVRPTR_START;
|
|
|
|
buffer1[4U] = m_txCounter;
|
|
buffer1[5U] = 0x00U;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer1 + 0U, 6U);
|
|
cksum.result(buffer1 + 6U);
|
|
} else {
|
|
buffer1[6U] = 0x00U;
|
|
buffer1[7U] = 0x0BU;
|
|
}
|
|
|
|
unsigned char buffer2[60U];
|
|
|
|
buffer2[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer2[1U] = 0x2FU;
|
|
buffer2[2U] = 0x00U;
|
|
|
|
buffer2[3U] = DVRPTR_HEADER;
|
|
|
|
buffer2[4U] = m_txCounter;
|
|
buffer2[5U] = 0x00U;
|
|
|
|
buffer2[6U] = 0x00U;
|
|
buffer2[7U] = 0x00U;
|
|
|
|
::memset(buffer2 + 8U, ' ', RADIO_HEADER_LENGTH_BYTES);
|
|
|
|
buffer2[8U] = header.getFlag1();
|
|
buffer2[9U] = header.getFlag2();
|
|
buffer2[10U] = header.getFlag3();
|
|
|
|
std::string rpt2 = header.getRptCall2();
|
|
for (unsigned int i = 0U; i < rpt2.size() && i < LONG_CALLSIGN_LENGTH; i++)
|
|
buffer2[i + 11U] = rpt2[i];
|
|
|
|
std::string rpt1 = header.getRptCall1();
|
|
for (unsigned int i = 0U; i < rpt1.size() && i < LONG_CALLSIGN_LENGTH; i++)
|
|
buffer2[i + 19U] = rpt1[i];
|
|
|
|
std::string your = header.getYourCall();
|
|
for (unsigned int i = 0U; i < your.size() && i < LONG_CALLSIGN_LENGTH; i++)
|
|
buffer2[i + 27U] = your[i];
|
|
|
|
std::string my1 = header.getMyCall1();
|
|
for (unsigned int i = 0U; i < my1.size() && i < LONG_CALLSIGN_LENGTH; i++)
|
|
buffer2[i + 35U] = my1[i];
|
|
|
|
std::string my2 = header.getMyCall2();
|
|
for (unsigned int i = 0U; i < my2.size() && i < SHORT_CALLSIGN_LENGTH; i++)
|
|
buffer2[i + 43U] = my2[i];
|
|
|
|
CCCITTChecksumReverse cksum1;
|
|
cksum1.update(buffer2 + 8U, RADIO_HEADER_LENGTH_BYTES - 2U);
|
|
cksum1.result(buffer2 + 47U);
|
|
|
|
buffer2[49U] = 0x00U;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer2 + 0U, 50U);
|
|
cksum.result(buffer2 + 50U);
|
|
} else {
|
|
buffer2[50U] = 0x00U;
|
|
buffer2[51U] = 0x0BU;
|
|
}
|
|
|
|
m_pktCounter = 0U;
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
unsigned char type1 = DSMTT_START;
|
|
m_txData.addData(&type1, 1U);
|
|
|
|
unsigned char len1 = 8U;
|
|
m_txData.addData(&len1, 1U);
|
|
|
|
m_txData.addData(buffer1, 8U);
|
|
|
|
unsigned char type2 = DSMTT_HEADER;
|
|
m_txData.addData(&type2, 1U);
|
|
|
|
unsigned char len2 = 52U;
|
|
m_txData.addData(&len2, 1U);
|
|
|
|
m_txData.addData(buffer2, 52U);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDVMegaController::writeData(const unsigned char* data, unsigned int, bool end)
|
|
{
|
|
if (!m_txEnabled)
|
|
return false;
|
|
|
|
bool ret = m_txData.hasSpace(26U);
|
|
if (!ret) {
|
|
wxLogWarning("No space to write data");
|
|
return false;
|
|
}
|
|
|
|
unsigned char buffer[30U];
|
|
|
|
if (end) {
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x03U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_EOT;
|
|
|
|
buffer[4U] = m_txCounter;
|
|
buffer[5U] = 0xFFU;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 6U);
|
|
cksum.result(buffer + 6U);
|
|
} else {
|
|
buffer[6U] = 0x00U;
|
|
buffer[7U] = 0x0BU;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
unsigned char type = DSMTT_EOT;
|
|
m_txData.addData(&type, 1U);
|
|
|
|
unsigned char len = 8U;
|
|
m_txData.addData(&len, 1U);
|
|
|
|
m_txData.addData(buffer, 8U);
|
|
|
|
return true;
|
|
}
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x13U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_DATA;
|
|
|
|
buffer[4U] = m_txCounter;
|
|
buffer[5U] = m_pktCounter;
|
|
|
|
m_pktCounter++;
|
|
if (m_pktCounter >= m_txSpace)
|
|
m_pktCounter = 0U;
|
|
|
|
buffer[6U] = 0x00U;
|
|
buffer[7U] = 0x00U;
|
|
|
|
::memcpy(buffer + 8U, data, DV_FRAME_LENGTH_BYTES);
|
|
|
|
buffer[20U] = 0x00U;
|
|
buffer[21U] = 0x00U;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 22U);
|
|
cksum.result(buffer + 22U);
|
|
} else {
|
|
buffer[22U] = 0x00U;
|
|
buffer[23U] = 0x0BU;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
unsigned char type = DSMTT_DATA;
|
|
m_txData.addData(&type, 1U);
|
|
|
|
unsigned char len = 24U;
|
|
m_txData.addData(&len, 1U);
|
|
|
|
m_txData.addData(buffer, 24U);
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int CDVMegaController::getSpace()
|
|
{
|
|
return m_txData.freeSpace() / 26U;
|
|
}
|
|
|
|
bool CDVMegaController::isTXReady()
|
|
{
|
|
if (m_tx)
|
|
return false;
|
|
|
|
return m_txData.isEmpty();
|
|
}
|
|
|
|
bool CDVMegaController::readVersion()
|
|
{
|
|
for (unsigned int i = 0U; i < 6U; i++) {
|
|
unsigned char buffer[10U];
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x01U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_GET_VERSION;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 4U);
|
|
cksum.result(buffer + 4U);
|
|
} else {
|
|
buffer[4U] = 0x00U;
|
|
buffer[5U] = 0x0BU;
|
|
}
|
|
|
|
// CUtils::dump("Written", buffer, 6U);
|
|
|
|
int ret = m_serial.write(buffer, 6U);
|
|
if (ret != 6)
|
|
return false;
|
|
|
|
for (unsigned int count = 0U; count < MAX_RESPONSES; count++) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
unsigned int length;
|
|
RESP_TYPE_MEGA resp = getResponse(m_buffer, length);
|
|
if (resp == RTM_GET_VERSION) {
|
|
char firmware[32];
|
|
if ((m_buffer[4U] & 0x0FU) > 0x00U)
|
|
::snprintf(firmware, sizeof(firmware), "%u.%u%u%c",
|
|
(m_buffer[5U] & 0xF0U) >> 4,
|
|
m_buffer[5U] & 0x0FU,
|
|
(m_buffer[4U] & 0xF0U) >> 4,
|
|
(m_buffer[4U] & 0x0FU) + 'a' - 1U);
|
|
else
|
|
::snprintf(firmware, sizeof(firmware), "%u.%u%u",
|
|
(m_buffer[5U] & 0xF0U) >> 4,
|
|
m_buffer[5U] & 0x0FU,
|
|
(m_buffer[4U] & 0xF0U) >> 4);
|
|
|
|
std::string hardware((char*)(m_buffer + 6U), length - DVRPTR_HEADER_LENGTH - 3U);
|
|
|
|
wxLogInfo("DVMEGA Firmware version: %s, hardware: %s", firmware, hardware.c_str());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
}
|
|
|
|
wxLogError("Unable to read the firmware version after six attempts");
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CDVMegaController::readStatus()
|
|
{
|
|
unsigned char buffer[10U];
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x01U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_GET_STATUS;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 4U);
|
|
cksum.result(buffer + 4U);
|
|
} else {
|
|
buffer[4U] = 0x00U;
|
|
buffer[5U] = 0x0BU;
|
|
}
|
|
|
|
return m_serial.write(buffer, 6U) == 6;
|
|
}
|
|
|
|
bool CDVMegaController::setConfig()
|
|
{
|
|
unsigned char buffer[20U];
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x07U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_SET_CONFIG;
|
|
|
|
buffer[4U] = 0xC0U; // Physical layer
|
|
|
|
buffer[5U] = 0x04U; // Block length
|
|
|
|
buffer[6U] = 0x00U;
|
|
if (m_rxInvert)
|
|
buffer[6U] |= 0x01U;
|
|
if (m_txInvert)
|
|
buffer[6U] |= 0x02U;
|
|
|
|
uint16_t txDelay = htole16((uint16_t)m_txDelay);
|
|
::memcpy(buffer + 8U, &txDelay, sizeof(uint16_t));
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 10U);
|
|
cksum.result(buffer + 10U);
|
|
} else {
|
|
buffer[10U] = 0x00U;
|
|
buffer[11U] = 0x0BU;
|
|
}
|
|
|
|
// CUtils::dump("Written", buffer, 12U);
|
|
|
|
int ret = m_serial.write(buffer, 12U);
|
|
if (ret != 12)
|
|
return false;
|
|
|
|
unsigned int count = 0U;
|
|
unsigned int length;
|
|
RESP_TYPE_MEGA resp;
|
|
do {
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
resp = getResponse(m_buffer, length);
|
|
|
|
if (resp != RTM_SET_CONFIG) {
|
|
count++;
|
|
if (count >= MAX_RESPONSES) {
|
|
wxLogError("The DVMEGA is not responding to the SET_CONFIG command");
|
|
return false;
|
|
}
|
|
}
|
|
} while (resp != RTM_SET_CONFIG);
|
|
|
|
// CUtils::dump("Response", m_buffer, length);
|
|
|
|
unsigned char type = m_buffer[4U];
|
|
if (type != DVRPTR_ACK) {
|
|
wxLogError("Received a NAK to the SET_CONFIG command from the modem");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDVMegaController::setFrequencyAndPower()
|
|
{
|
|
unsigned char buffer[25U];
|
|
|
|
::memset(buffer, 0x00U, 21U);
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x10U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_SET_CONFIG;
|
|
|
|
buffer[4U] = 0xC1U; // RF layer
|
|
|
|
buffer[5U] = 0x0CU; // Block length
|
|
|
|
uint32_t rxFreq = htole32((uint32_t)m_rxFrequency);
|
|
uint32_t txFreq = htole32((uint32_t)m_txFrequency);
|
|
|
|
::memcpy(buffer + 7U, &rxFreq, sizeof(uint32_t));
|
|
::memcpy(buffer + 11U, &txFreq, sizeof(uint32_t));
|
|
|
|
buffer[16U] = (m_power * 64U) / 100U;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 19U);
|
|
cksum.result(buffer + 19U);
|
|
} else {
|
|
buffer[19U] = 0x00U;
|
|
buffer[20U] = 0x0BU;
|
|
}
|
|
|
|
// CUtils::dump("Written", buffer, 21U);
|
|
|
|
int ret = m_serial.write(buffer, 21U);
|
|
if (ret != 21)
|
|
return false;
|
|
|
|
unsigned int count = 0U;
|
|
unsigned int length;
|
|
RESP_TYPE_MEGA resp;
|
|
do {
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
resp = getResponse(m_buffer, length);
|
|
|
|
if (resp != RTM_SET_CONFIG) {
|
|
count++;
|
|
if (count >= MAX_RESPONSES) {
|
|
wxLogError("The DVMEGA is not responding to the SET_CONFIG command");
|
|
return false;
|
|
}
|
|
}
|
|
} while (resp != RTM_SET_CONFIG);
|
|
|
|
// CUtils::dump("Response", m_buffer, length);
|
|
|
|
unsigned char type = m_buffer[4U];
|
|
if (type != DVRPTR_ACK) {
|
|
wxLogError("Received a NAK to the SET_CONFIG command from the modem");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDVMegaController::setEnabled(bool enable)
|
|
{
|
|
unsigned char buffer[10U];
|
|
|
|
buffer[0U] = DVRPTR_FRAME_START;
|
|
|
|
buffer[1U] = 0x02U;
|
|
buffer[2U] = 0x00U;
|
|
|
|
buffer[3U] = DVRPTR_GET_STATUS;
|
|
|
|
// Enable RX, TX, and Watchdog
|
|
if (enable)
|
|
buffer[4U] = 0x01U | 0x02U | 0x04U;
|
|
else
|
|
buffer[4U] = 0x00U;
|
|
|
|
if (m_checksum) {
|
|
CCCITTChecksum cksum;
|
|
cksum.update(buffer + 0U, 5U);
|
|
cksum.result(buffer + 5U);
|
|
} else {
|
|
buffer[5U] = 0x00U;
|
|
buffer[6U] = 0x0BU;
|
|
}
|
|
|
|
// CUtils::dump("Written", buffer, 7U);
|
|
|
|
int ret = m_serial.write(buffer, 7U);
|
|
if (ret != 7)
|
|
return false;
|
|
|
|
unsigned int count = 0U;
|
|
unsigned int length;
|
|
RESP_TYPE_MEGA resp;
|
|
do {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
resp = getResponse(m_buffer, length);
|
|
|
|
if (resp != RTM_GET_STATUS) {
|
|
count++;
|
|
if (count >= MAX_RESPONSES) {
|
|
wxLogError("The DVMEGA is not responding to the SET_STATUS command");
|
|
return false;
|
|
}
|
|
}
|
|
} while (resp != RTM_GET_STATUS);
|
|
|
|
// CUtils::dump("Response", m_buffer, length);
|
|
|
|
unsigned char type = m_buffer[4U];
|
|
if (type != DVRPTR_ACK) {
|
|
wxLogError("Received a NAK to the SET_STATUS command from the modem");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Reads one complete DVRPTR frame.
|
|
// Frame header (5 bytes): [0xD0] [len_lo] [len_hi] [type] [...]
|
|
// length = header bytes 1+2 (payload length, not including header itself).
|
|
// type has bit 7 set for board responses; strip it before dispatching.
|
|
// Returns RTM_TIMEOUT if no start byte is available, RTM_ERROR on I/O failure.
|
|
RESP_TYPE_MEGA CDVMegaController::getResponse(unsigned char *buffer, unsigned int& length)
|
|
{
|
|
// Get the start of the frame or nothing at all
|
|
int ret = m_serial.read(buffer, 1U);
|
|
if (ret < 0) {
|
|
wxLogError("Error when reading from the DVMEGA");
|
|
return RTM_ERROR;
|
|
}
|
|
|
|
if (ret == 0)
|
|
return RTM_TIMEOUT;
|
|
|
|
if (buffer[0U] != DVRPTR_FRAME_START)
|
|
return RTM_TIMEOUT;
|
|
|
|
unsigned int offset = 1U;
|
|
|
|
while (offset < DVRPTR_HEADER_LENGTH) {
|
|
ret = m_serial.read(buffer + offset, DVRPTR_HEADER_LENGTH - offset);
|
|
if (ret < 0) {
|
|
wxLogError("Error when reading from the DVMEGA");
|
|
return RTM_ERROR;
|
|
}
|
|
|
|
if (ret > 0)
|
|
offset += ret;
|
|
|
|
if (ret == 0) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
if (m_stopped)
|
|
return RTM_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
length = buffer[1U] + buffer[2U] * 256U;
|
|
|
|
if (length >= 100U) {
|
|
wxLogError("Invalid data received from the DVMEGA");
|
|
return RTM_ERROR;
|
|
}
|
|
|
|
// Remove the response bit
|
|
unsigned int type = buffer[3U] & 0x7FU;
|
|
|
|
offset = 0U;
|
|
|
|
while (offset < length) {
|
|
ret = m_serial.read(buffer + offset + DVRPTR_HEADER_LENGTH, length - offset);
|
|
if (ret < 0) {
|
|
wxLogError("Error when reading from the DVMEGA");
|
|
return RTM_ERROR;
|
|
}
|
|
|
|
if (ret > 0)
|
|
offset += ret;
|
|
|
|
if (ret == 0) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
if (m_stopped)
|
|
return RTM_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
length += DVRPTR_HEADER_LENGTH;
|
|
|
|
// CUtils::dump("Received", buffer, length);
|
|
|
|
switch (type) {
|
|
case DVRPTR_GET_STATUS:
|
|
return RTM_GET_STATUS;
|
|
case DVRPTR_GET_VERSION:
|
|
return RTM_GET_VERSION;
|
|
case DVRPTR_GET_SERIAL:
|
|
return RTM_GET_SERIAL;
|
|
case DVRPTR_GET_CONFIG:
|
|
return RTM_GET_CONFIG;
|
|
case DVRPTR_SET_CONFIG:
|
|
return RTM_SET_CONFIG;
|
|
case DVRPTR_RXPREAMBLE:
|
|
return RTM_RXPREAMBLE;
|
|
case DVRPTR_START:
|
|
return RTM_START;
|
|
case DVRPTR_HEADER:
|
|
return RTM_HEADER;
|
|
case DVRPTR_RXSYNC:
|
|
return RTM_RXSYNC;
|
|
case DVRPTR_DATA:
|
|
return RTM_DATA;
|
|
case DVRPTR_EOT:
|
|
return RTM_EOT;
|
|
case DVRPTR_RXLOST:
|
|
return RTM_RXLOST;
|
|
case DVRPTR_SET_TESTMDE:
|
|
return RTM_SET_TESTMDE;
|
|
default:
|
|
return RTM_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
std::string CDVMegaController::getPath() const
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
bool CDVMegaController::findPort()
|
|
{
|
|
if (m_path.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);
|
|
// Get all but the last section
|
|
size_t pos = fullPath.rfind('/');
|
|
path = (pos != std::string::npos) ? fullPath.substr(0, pos) : fullPath;
|
|
}
|
|
|
|
if (path == m_path) {
|
|
m_port = "/dev/" + fileName;
|
|
|
|
wxLogMessage("Found modem port of %s based on the path", m_port.c_str());
|
|
|
|
::closedir(dir);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
::closedir(dir);
|
|
return false;
|
|
}
|
|
|
|
bool CDVMegaController::findPath()
|
|
{
|
|
std::string path = "/sys/class/tty/" + m_port.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_path.empty())
|
|
wxLogMessage("Found modem path of %s", path.c_str());
|
|
|
|
m_path = path;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDVMegaController::findModem()
|
|
{
|
|
m_serial.close();
|
|
|
|
// Tell the repeater that the signal has gone away
|
|
if (m_rx) {
|
|
std::lock_guard<std::mutex> 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 CDVMegaController::openModem()
|
|
{
|
|
bool ret = m_serial.open();
|
|
if (!ret)
|
|
return false;
|
|
|
|
ret = readVersion();
|
|
if (!ret) {
|
|
m_serial.close();
|
|
return false;
|
|
}
|
|
|
|
ret = setConfig();
|
|
if (!ret) {
|
|
m_serial.close();
|
|
return false;
|
|
}
|
|
|
|
if (m_rxFrequency != 0U && m_txFrequency != 0U) {
|
|
ret = setFrequencyAndPower();
|
|
if (!ret) {
|
|
m_serial.close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ret = setEnabled(true);
|
|
if (!ret) {
|
|
m_serial.close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|