diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 97542b29..8967bb34 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -26,7 +26,7 @@ SET(libdmrconf_SOURCES gd77.cc gd77_codeplug.cc gd77_callsigndb.cc gd77_filereader.cc gd77_limits.cc opengd77.cc opengd77_interface.cc opengd77_codeplug.cc opengd77_extension.cc opengd77_callsigndb.cc opengd77_limits.cc - openrtx.cc openrtx_interface.cc openrtx_codeplug.cc + openrtx.cc openrtx_link.cc openrtx_interface.cc openrtx_codeplug.cc anytone_interface.cc anytone_radio.cc anytone_codeplug.cc anytone_extension.cc anytone_limits.cc d868uv.cc d868uv_codeplug.cc d868uv_callsigndb.cc d868uv_limits.cc d878uv.cc d878uv_codeplug.cc d878uv_limits.cc @@ -50,7 +50,7 @@ SET(libdmrconf_MOC_HEADERS gd77.hh gd77_codeplug.hh gd77_callsigndb.hh gd77_limits.hh opengd77.hh opengd77_interface.hh opengd77_codeplug.hh opengd77_extension.hh opengd77_callsigndb.hh opengd77_limits.hh - openrtx.hh openrtx_interface.hh openrtx_codeplug.hh + openrtx.hh openrtx_link.hh openrtx_interface.hh openrtx_codeplug.hh anytone_interface.hh anytone_radio.hh anytone_codeplug.hh anytone_extension.hh anytone_limits.hh d868uv.hh d868uv_codeplug.hh d868uv_callsigndb.hh d868uv_limits.hh d878uv.hh d878uv_codeplug.hh d878uv_limits.hh diff --git a/lib/openrtx_interface.cc b/lib/openrtx_interface.cc index 20a6bc25..b50319e1 100644 --- a/lib/openrtx_interface.cc +++ b/lib/openrtx_interface.cc @@ -1,7 +1,7 @@ #include "openrtx_interface.hh" OpenRTXInterface::OpenRTXInterface(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) - : DFUDevice(descr, err, parent), RadioInterface() + : USBSerial(descr, err, parent) { // pass... } @@ -9,12 +9,12 @@ OpenRTXInterface::OpenRTXInterface(const USBDeviceDescriptor &descr, const Error bool OpenRTXInterface::isOpen() const { - return DFUDevice::isOpen(); + return USBSerial::isOpen(); } void OpenRTXInterface::close() { - DFUDevice::close(); + USBSerial::close(); } diff --git a/lib/openrtx_interface.hh b/lib/openrtx_interface.hh index 92bc5234..4d32990e 100644 --- a/lib/openrtx_interface.hh +++ b/lib/openrtx_interface.hh @@ -2,16 +2,26 @@ #define OPENRTXINTERFACE_HH #include "radiointerface.hh" -#include "dfu_libusb.hh" +#include "packetstream.hh" +#include "usbserial.hh" +#include "xmodem.hh" /** Implements the communication interface to radios running the OpenRTX firmware. * + * The protocol is called rtxlink and is documented at https://openrtx.org/#/rtxlink. The protocol + * has several layers. The lowest is a serial interface either as a VCOM (UBS CDC-ACM) or a proper + * hardware UART. Ontop of that, there is SLIP. Followed by a simple framing layer, that determines + * the higher-level protocol. * @ingroup ortx */ -class OpenRTXInterface : public DFUDevice, public RadioInterface +class OpenRTXInterface : public USBSerial { Q_OBJECT public: + /** Constructor. + * @param descr The USB device descriptor. Used to identify a specific USB device. + * @param err The stack of error messages. + * @param parent The QObject parent. */ explicit OpenRTXInterface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent = nullptr); bool isOpen() const; @@ -28,8 +38,6 @@ public: bool write_finish(const ErrorStack &err=ErrorStack()); bool reboot(const ErrorStack &err=ErrorStack()); - - }; #endif // OPENRTXINTERFACE_HH diff --git a/lib/openrtx_link.cc b/lib/openrtx_link.cc new file mode 100644 index 00000000..08faf3fe --- /dev/null +++ b/lib/openrtx_link.cc @@ -0,0 +1,165 @@ +#include "openrtx_link.hh" + + +/* ******************************************************************************************** * + * Implements the OpenRTX link stream socket + * ******************************************************************************************** */ +OpenRTXLinkStream::OpenRTXLinkStream(OpenRTXLink::Protocol proto, OpenRTXLink *link) + : QIODevice(link), _proto(proto), _inBuffer(), _link(link) +{ + // pass... +} + +qint64 +OpenRTXLinkStream::writeData(const char *data, qint64 len) { + QByteArray buffer(data, len); + + ErrorStack err; + if (! _link->send(OpenRTXLink::Protocol::Stdio, buffer, -1, err)) { + setErrorString(err.format()); + return -1; + } + + return len; +} + +qint64 +OpenRTXLinkStream::readData(char *data, qint64 maxlen) { + ErrorStack err; + qint64 n_read = 0, n_toread=maxlen; + + while (0 < n_toread) { + if (_inBuffer.size()) { + qint64 n = std::min((qint64)_inBuffer.size(), n_toread); + memcpy(data, _inBuffer.data(), n); + _inBuffer.remove(0, n); + n_toread -= n; data+= n; n_read += n; + } + if (0 == n_toread) + return n_read; + if (! _link->receive(OpenRTXLink::Protocol::Stdio, _inBuffer, -1, err)) { + setErrorString(err.format()); + return -1; + } + } + + return n_read; +} + + + +/* ******************************************************************************************** * + * Implements the OpenRTX link datagram socket + * ******************************************************************************************** */ +OpenRTXLinkDatagram::OpenRTXLinkDatagram(OpenRTXLink::Protocol proto, OpenRTXLink *link) + : PacketStream(link), _proto(proto), _link(link) +{ + // pass... +} + +bool +OpenRTXLinkDatagram::receive(QByteArray &buffer, int timeout, const ErrorStack &err) { + return _link->receive(_proto, buffer, timeout, err); +} + +bool +OpenRTXLinkDatagram::send(const QByteArray &buffer, int timeout, const ErrorStack &err) { + return _link->send(_proto, buffer, timeout, err); +} + + +/* ******************************************************************************************** * + * Implements the OpenRTX CAT interface + * ******************************************************************************************** */ +OpenRTXCAT::OpenRTXCAT(OpenRTXLink *link) + : OpenRTXLinkDatagram(OpenRTXLink::Protocol::CAT, link) +{ + // pass... +} + + +/* ******************************************************************************************** * + * Implements the OpenRTX link protocol dispatcher + * ******************************************************************************************** */ +OpenRTXLink::OpenRTXLink(PacketStream *link, QObject *parent) + : QObject{parent}, _link(link), + _stdio(new OpenRTXLinkStream(Protocol::Stdio, this)), + _cat(new OpenRTXCAT(this)), + _fmp(new OpenRTXLinkDatagram(Protocol::FMP, this)), + _xmodem(new OpenRTXLinkStream(Protocol::XMODEM, this)) +{ + // pass... +} + +OpenRTXLinkStream * +OpenRTXLink::stdio() const { + return _stdio; +} + +OpenRTXCAT *OpenRTXLink::cat() const { + return _cat; +} + +OpenRTXLinkDatagram * +OpenRTXLink::fmp() const { + return _fmp; +} + +OpenRTXLinkStream * +OpenRTXLink::xmodem() const { + return _xmodem; +} + +uint8_t +OpenRTXLink::crc8(const QByteArray &data) { + constexpr uint8_t poly = 0xA6; + uint8_t crc = 0; + for (int i=0; i_link->send(packet, timeout, err); +} + +bool +OpenRTXLink::receive(Protocol proto, QByteArray &data, int timeout, const ErrorStack &err) { + QByteArray buffer; + while (true) { + // Receive a datagram + if (! this->_link->receive(buffer, timeout, err)) + return false; + + // Check CRC + if (0 != crc8(buffer)) { + errMsg(err) << "Invalid CRC in RTXLink packet."; + return false; + } + + // Dispatch by type + Protocol rxProto = (Protocol)buffer.at(0); + // If requested type matches -> done + if (proto == rxProto) { + data = buffer.mid(1, buffer.size()-2); + return true; + } + + // Otherwise, store and receive next + if (! _inBuffers.contains((unsigned int)rxProto)) + _inBuffers.insert((unsigned int)rxProto, QList()); + _inBuffers[(unsigned int)rxProto].append(buffer.mid(1,buffer.size()-2)); + } +} diff --git a/lib/openrtx_link.hh b/lib/openrtx_link.hh new file mode 100644 index 00000000..a2d698f6 --- /dev/null +++ b/lib/openrtx_link.hh @@ -0,0 +1,133 @@ +#ifndef OPENRTXLINK_HH +#define OPENRTXLINK_HH + +#include + +#include "packetstream.hh" + +// Forward declaration +class OpenRTXLink; +class OpenRTXLinkStream; +class OpenRTXLinkDatagram; +class OpenRTXCAT; + + +/** Implements the OpenRTX link protocol. This is a datagram-oriented protocol, that dispatches + * several different protocols to talk to the radio. It provides a stream, to the stdio of the + * radio, a CAT interface, a file-system management protocol (FMP) as well as XMODEM to transfer + * files. The file transfer must be initialized via the FMP. + * + * All these protocols are exposed through specialized interface objects, accessible through this + * classs. + * + * @code + * +------------------+-------------------+-------------------+------------------+ + * | stdio | CAT | FMP | XMODEM | + * +------------------+-------------------+-------------------+------------------+ + * | OpenRTXLink | + * +-----------------------------------------------------------------------------+ + * | SLIP (SlipStream) | + * +-----------------------------------------------------------------------------+ + * | USB CDC-ACM (VCOM/Serial-over-USB, USBSerial) | + * +-----------------------------------------------------------------------------+ + * @endcode + * @ingroup ortx */ +class OpenRTXLink: public QObject +{ + Q_OBJECT + +protected: + /** The possible protocols, encapsulated in OpenRTX link. */ + enum class Protocol { + Stdio = 0, CAT = 1, FMP = 2, XMODEM = 3 + }; + +public: + /** Constructor. + * @param link Specifies the datagram socket to talk to the radio (SLIP). + * @param parent Specifies the QObject parent. */ + explicit OpenRTXLink(PacketStream *link, QObject *parent = nullptr); + + /** Returns a stream to the stdio of the radio. Implements the @c QIODevice interface. */ + OpenRTXLinkStream *stdio() const; + /** The CAT interface to the radio. */ + OpenRTXCAT *cat() const; + /** The file-system management protocol interface to the radio. */ + OpenRTXLinkDatagram *fmp() const; + /** An XMODEM channel to transfer files. */ + OpenRTXLinkStream *xmodem() const; + +protected: + /** Dispatcher to receive datagrams over OpenRTXLink. */ + bool receive(Protocol proto, QByteArray &data, int timeout=-1, const ErrorStack &err=ErrorStack()); + /** Dispatcher to send datagrams over OpenRTXLink. */ + bool send(Protocol proto, const QByteArray &data, int timeout=-1, const ErrorStack &err=ErrorStack()); + + static uint8_t crc8(const QByteArray &data); + +protected: + PacketStream *_link; + QHash> _inBuffers; + OpenRTXLinkStream *_stdio; + OpenRTXCAT *_cat; + OpenRTXLinkDatagram *_fmp; + OpenRTXLinkStream *_xmodem; + + friend class OpenRTXLinkStream; + friend class OpenRTXLinkDatagram; + friend class OpenRTXCAT; +}; + + +class OpenRTXLinkStream: public QIODevice +{ + Q_OBJECT + +protected: + OpenRTXLinkStream(OpenRTXLink::Protocol proto, OpenRTXLink *link); + +protected: + qint64 writeData(const char *data, qint64 len); + qint64 readData(char *data, qint64 maxlen); + +protected: + OpenRTXLink::Protocol _proto; + QByteArray _inBuffer; + OpenRTXLink *_link; + + friend class OpenRTXLink; +}; + + +class OpenRTXLinkDatagram: public PacketStream +{ + Q_OBJECT + +protected: + OpenRTXLinkDatagram(OpenRTXLink::Protocol proto, OpenRTXLink *link); + +public: + bool receive(QByteArray &buffer, int timeout, const ErrorStack &err); + bool send(const QByteArray &buffer, int timeout, const ErrorStack &err); + +protected: + OpenRTXLink::Protocol _proto; + OpenRTXLink *_link; + + friend class OpenRTXLink; +}; + + +class OpenRTXCAT: public OpenRTXLinkDatagram +{ + Q_OBJECT + +protected: + /** Hidden constrcutor. An instance of this class can be obtained from @c OpenRTXLink. + * @param link Specifies the unerlying RTX-link to the the device. */ + explicit OpenRTXCAT(OpenRTXLink *link); + + friend class OpenRTXLink; +}; + +#endif // OPENRTXLINK_HH diff --git a/lib/packetstream.hh b/lib/packetstream.hh index f6e2e8f6..dee29489 100644 --- a/lib/packetstream.hh +++ b/lib/packetstream.hh @@ -53,4 +53,6 @@ private: static const char ESCAPED_DB = '\xDD'; }; + + #endif // PACKETSTREAM_HH diff --git a/lib/xmodem.cc b/lib/xmodem.cc index de944342..5a67cd22 100644 --- a/lib/xmodem.cc +++ b/lib/xmodem.cc @@ -2,8 +2,8 @@ #include "logger.hh" -XModem::XModem(const USBDeviceDescriptor& descriptor, const ErrorStack& err, QObject *parent) - : USBSerial(descriptor, err, parent), _state(State::Init), _maxRetry(10) +XModem::XModem(OpenRTXLinkStream *link, QObject *parent) + : QObject(parent), _link(link), _state(State::Init), _maxRetry(10) { // pass... } @@ -219,12 +219,9 @@ XModem::send(const QByteArray &buffer, int timeout, const ErrorStack &err) { bool XModem::txByte(uint8_t byte, int timeout, const ErrorStack &err) { - if (! putChar((char)byte)) { - errMsg(err) << "Cannot send byte: " << this->errorString(); - return false; - } - if (! waitForBytesWritten(timeout)) { - errMsg(err) << "Write time-out."; + Q_UNUSED(timeout) + if (1 != _link->write((const char *)&byte, 1)) { + errMsg(err) << "Cannot send byte: " << _link->errorString(); return false; } return true; @@ -232,12 +229,12 @@ XModem::txByte(uint8_t byte, int timeout, const ErrorStack &err) { bool XModem::rxByte(uint8_t &byte, int timeout, const ErrorStack &err) { - if ((! bytesAvailable()) && (!waitForReadyRead(timeout))) { + if ((! _link->bytesAvailable()) && (!_link->waitForReadyRead(timeout))) { errMsg(err) << "Read time-out."; return false; } - if (! getChar((char *)&byte)) { - errMsg(err) << "Cannot receive byte: " << this->errorString(); + if (! _link->getChar((char *)&byte)) { + errMsg(err) << "Cannot receive byte: " << _link->errorString(); return false; } return true; @@ -246,13 +243,13 @@ XModem::rxByte(uint8_t &byte, int timeout, const ErrorStack &err) { bool XModem::txBytes(const uint8_t *buffer, unsigned int length, int timeout, const ErrorStack &err) { while (length) { - qint64 nb_written = QSerialPort::write((const char *)buffer, length); + qint64 nb_written = _link->write((const char *)buffer, length); if (0 > nb_written) { - errMsg(err) << "Cannot write to interface: " << this->errorString(); + errMsg(err) << "Cannot write to interface: " << _link->errorString(); return false; } length -= nb_written; - if (! waitForBytesWritten(timeout)) { + if (! _link->waitForBytesWritten(timeout)) { errMsg(err) << "Read time-out."; return false; } @@ -264,13 +261,13 @@ XModem::txBytes(const uint8_t *buffer, unsigned int length, int timeout, const E bool XModem::rxBytes(uint8_t *buffer, unsigned int length, int timeout, const ErrorStack &err) { while (length) { - if ((! bytesAvailable()) && (!waitForReadyRead(timeout))) { + if ((! _link->bytesAvailable()) && (!_link->waitForReadyRead(timeout))) { errMsg(err) << "Read time-out."; return false; } - qint64 nb_read = QSerialPort::read((char *)buffer, length); + qint64 nb_read = _link->read((char *)buffer, length); if (0 > nb_read) { - errMsg(err) << "Cannot read from interface: " << this->errorString(); + errMsg(err) << "Cannot read from interface: " << _link->errorString(); return false; } length -= nb_read; diff --git a/lib/xmodem.hh b/lib/xmodem.hh index 6200937f..e2ab2904 100644 --- a/lib/xmodem.hh +++ b/lib/xmodem.hh @@ -1,14 +1,14 @@ #ifndef XMODEM_HH #define XMODEM_HH -#include "usbserial.hh" +#include "openrtx_link.hh" -/** Implements the XMODEM protocol (1k + crc16 variant) for USB-Serial devices. +/** Implements the XMODEM protocol (1k + crc16 variant) for a packet stream. * * Provides two methods to send and receive an entire "file". * * @ingroup rif */ -class XModem : public USBSerial +class XModem : public QObject { Q_OBJECT @@ -25,7 +25,7 @@ protected: public: /** Constructs a xmodem connection via the USB device specified by @c descriptor. */ - explicit XModem(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); + explicit XModem(OpenRTXLinkStream *transferLayer, QObject *parent=nullptr); /** Receives an entire file from the device. * @param buffer The buffer to store the data in. The contents of the buffer will be cleared. @@ -54,6 +54,8 @@ protected: static uint16_t crc_ccitt(const QByteArray &data); private: + /// A weak reference to the transfer layer. + OpenRTXLinkStream *_link; /// State of the state machine. State _state; /// Maximum number of retires.