From 247ca753f1bf90f7f6de283e3fc457a611143f9f Mon Sep 17 00:00:00 2001 From: jgromes Date: Fri, 21 Jul 2023 18:18:05 +0200 Subject: [PATCH] [APRS] Added support for APRS over LoRa --- src/protocols/APRS/APRS.cpp | 58 ++++++++++++++++++++++++++++++------- src/protocols/APRS/APRS.h | 21 ++++++++++++-- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/protocols/APRS/APRS.cpp b/src/protocols/APRS/APRS.cpp index 8ae93e8c..a7300bc9 100644 --- a/src/protocols/APRS/APRS.cpp +++ b/src/protocols/APRS/APRS.cpp @@ -6,9 +6,15 @@ APRSClient::APRSClient(AX25Client* ax) { axClient = ax; + phyLayer = nullptr; } -int16_t APRSClient::begin(char sym, bool alt) { +APRSClient::APRSClient(PhysicalLayer* phy) { + axClient = nullptr; + phyLayer = phy; +} + +int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) { RADIOLIB_CHECK_RANGE(sym, ' ', '}', RADIOLIB_ERR_INVALID_SYMBOL); symbol = sym; @@ -18,6 +24,16 @@ int16_t APRSClient::begin(char sym, bool alt) { table = '/'; } + if((!src) && (this->phyLayer != nullptr)) { + return(RADIOLIB_ERR_INVALID_CALLSIGN); + } + + if(strlen(callsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_CALLSIGN); + } + memcpy(this->src, callsign, strlen(callsign)); + this->id = ssid; + return(RADIOLIB_ERR_NONE); } @@ -30,7 +46,7 @@ int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, char* lat if(time != NULL) { len += strlen(time); } - char* info = new char[len]; + char* info = new char[len + 1]; #else char info[RADIOLIB_STATIC_ARRAY_SIZE]; #endif @@ -49,6 +65,7 @@ int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, char* lat // timestamp and message sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "%s%s%c%s%c%s", time, lat, table, lon, symbol, msg); } + info[len] = '\0'; // send the frame int16_t state = sendFrame(destCallsign, destSSID, info); @@ -211,7 +228,12 @@ int16_t APRSClient::sendMicE(float lat, float lon, uint16_t heading, uint16_t sp info[infoPos++] = '\0'; // send the frame - int16_t state = sendFrame(destCallsign, 0, info); + uint8_t destSSID = 0; + if(this->phyLayer != nullptr) { + // TODO make SSID configurable? + destSSID = 1; + } + int16_t state = sendFrame(destCallsign, destSSID, info); #if !defined(RADIOLIB_STATIC_ONLY) delete[] info; #endif @@ -219,15 +241,31 @@ int16_t APRSClient::sendMicE(float lat, float lon, uint16_t heading, uint16_t sp } int16_t APRSClient::sendFrame(char* destCallsign, uint8_t destSSID, char* info) { - // get AX.25 callsign - char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; - axClient->getCallsign(srcCallsign); + // encoding depends on whether AX.25 should be used or not + if(this->axClient != nullptr) { + // AX.25/classical mode, get AX.25 callsign + char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; + axClient->getCallsign(srcCallsign); - AX25Frame frameUI(destCallsign, destSSID, srcCallsign, axClient->getSSID(), RADIOLIB_AX25_CONTROL_U_UNNUMBERED_INFORMATION | - RADIOLIB_AX25_CONTROL_POLL_FINAL_DISABLED | RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME, - RADIOLIB_AX25_PID_NO_LAYER_3, (const char*)info); + AX25Frame frameUI(destCallsign, destSSID, srcCallsign, axClient->getSSID(), RADIOLIB_AX25_CONTROL_U_UNNUMBERED_INFORMATION | + RADIOLIB_AX25_CONTROL_POLL_FINAL_DISABLED | RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME, + RADIOLIB_AX25_PID_NO_LAYER_3, (const char*)info); - return(axClient->sendFrame(&frameUI)); + return(axClient->sendFrame(&frameUI)); + + } else if(this->phyLayer != nullptr) { + // non-AX.25/LoRa mode + size_t len = RADIOLIB_APRS_LORA_HEADER_LEN + strlen(this->src) + 4 + strlen(destCallsign) + 11 + strlen(info); + Serial.println(len); + uint8_t* buff = new uint8_t[len]; + snprintf(buff, len, RADIOLIB_APRS_LORA_HEADER "%s-%d>%s,WIDE%d-%d:%s", this->src, this->id, destCallsign, destSSID, destSSID, info); + + int16_t res = this->phyLayer->transmit(buff, strlen(buff)); + delete[] buff; + return(res); + } + + return(RADIOLIB_ERR_WRONG_MODEM); } #endif diff --git a/src/protocols/APRS/APRS.h b/src/protocols/APRS/APRS.h index cf1abbde..ee74cfa0 100644 --- a/src/protocols/APRS/APRS.h +++ b/src/protocols/APRS/APRS.h @@ -58,6 +58,10 @@ // alias for unused altitude in Mic-E #define RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED -1000000 +// special header applied for APRS over LoRa +#define RADIOLIB_APRS_LORA_HEADER "<\xff\x01" +#define RADIOLIB_APRS_LORA_HEADER_LEN (3) + /*! \class APRSClient \brief Client for APRS communication. @@ -65,20 +69,28 @@ class APRSClient { public: /*! - \brief Default constructor. + \brief Constructor for "classic" mode using AX.25/AFSK. \param ax Pointer to the instance of AX25Client to be used for APRS. */ explicit APRSClient(AX25Client* ax); + /*! + \brief Constructor for LoRa mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit APRSClient(PhysicalLayer* phy); + // basic methods /*! \brief Initialization method. \param sym APRS symbol to be displayed. + \param callsign Source callsign. Required and only used for APRS over LoRa, ignored in classic mode. + \param ssid Source SSID. Only used for APRS over LoRa, ignored in classic mode, defaults to 0. \param alt Whether to use the primary (false) or alternate (true) symbol table. Defaults to primary table. \returns \ref status_codes */ - int16_t begin(char sym, bool alt = false); + int16_t begin(char sym, char* callsign = NULL, uint8_t ssid = 0, bool alt = false); /*! \brief Transmit position. @@ -120,10 +132,15 @@ class APRSClient { private: #endif AX25Client* axClient; + PhysicalLayer* phyLayer; // default APRS symbol (car) char symbol = '>'; char table = '/'; + + // source callsign when using APRS over LoRa + char src[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1] = { 0 }; + uint8_t id = 0; }; #endif