From 9c06c93ec0e8056c3bc30d6ace10e6f0621732de Mon Sep 17 00:00:00 2001 From: sh123 Date: Sun, 14 Jun 2020 14:52:22 +0300 Subject: [PATCH] Refactor callsign management into separate module and add support for baseic WIDEn-n digirepating --- ax25_callsign.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++++ ax25_callsign.h | 42 ++++++++++++++ ax25_payload.cpp | 110 +++++++++++------------------------- ax25_payload.h | 22 ++++---- esp32_loraprs.ino | 1 + loraprs.cpp | 56 ++++++++++-------- loraprs.h | 8 ++- 7 files changed, 268 insertions(+), 112 deletions(-) create mode 100644 ax25_callsign.cpp create mode 100644 ax25_callsign.h diff --git a/ax25_callsign.cpp b/ax25_callsign.cpp new file mode 100644 index 0000000..a33cad2 --- /dev/null +++ b/ax25_callsign.cpp @@ -0,0 +1,141 @@ +#include "ax25_callsign.h" + +namespace AX25 { + +Callsign::Callsign() + : isValid_(false) + , call_() + , ssid_(0) +{ +} + +Callsign::Callsign(const Callsign &callsign) + : isValid_(callsign.isValid_) + , call_(callsign.call_) + , ssid_(callsign.ssid_) +{ +} + +Callsign& Callsign::operator=(const Callsign &callsign) +{ + isValid_ = callsign.isValid_; + call_ = callsign.call_; + ssid_ = callsign.ssid_; + return *this; +} + +Callsign::Callsign(const byte *rxPayload, int payloadLength) + : isValid_(false) + , call_() + , ssid_(0) +{ + isValid_ = fromBinary(rxPayload, payloadLength); +} + +Callsign::Callsign(String callsign) + : isValid_(false) + , call_() + , ssid_(0) +{ + isValid_ = fromString(callsign); +} + +bool Callsign::ToBinary(byte *txPayload, int bufferLength) const +{ + return encode(txPayload, bufferLength); +} + +String Callsign::ToString() const +{ + String result = call_; + if (ssid_ != 0) { + result += "-" + String(ssid_); + } + return result; +} + +bool Callsign::Digirepeat(const String &ownCallsign) +{ + // only WIDE supported + if (call_.startsWith("WIDE*")) return false; + if (call_.startsWith("WIDE") && call_.length() >= 5) { + char wideLevel = call_.charAt(4); + if ((wideLevel == '1' || wideLevel == '2' || wideLevel == '3') && ssid_ > 0) { + if (--ssid_ == 0) { + call_ += "*"; + } + return true; + } + } + return false; +} + +bool Callsign::encode(byte *txPtr, int bufferLength) const +{ + if (bufferLength < CallsignSize) return false; + + byte *ptr = txPtr; + + memset(ptr, 0, bufferLength); + + for (int i = 0; i < CallsignSize - 1; i++) { + if (i < call_.length()) { + char c = call_.charAt(i); + *(ptr++) = c << 1; + } + else { + *(ptr++) = char(' ') << 1; + } + } + *(txPtr + CallsignSize - 1) = ssid_ << 1; + + return true; +} + +bool Callsign::fromString(String callsign) +{ + // "ABCDEF-XX" + if (callsign.length() > CallsignSize + 2 || callsign.length() == 0) return false; + + int delimIndex = callsign.indexOf('-'); + // "ABCDEF-" + if (delimIndex = callsign.length() - 1) return false; + + call_ = callsign; + ssid_ = 0; + + if (delimIndex == -1) { + // "ABCDEFG" + if (callsign.length() >= CallsignSize) return false; + } + else { + call_ = callsign.substring(0, delimIndex); + ssid_ = callsign.substring(delimIndex + 1).toInt(); + } + + return true; +} + +bool Callsign::fromBinary(const byte *rxPtr, int length) +{ + if (length < CallsignSize) return false; + + byte callsign[CallsignSize]; + + const byte *ptr = rxPtr; + + memset(callsign, 0, sizeof(callsign)); + + for (int i = 0; i < CallsignSize - 1; i++) { + char c = *(ptr++) >> 1; + callsign[i] = (c == ' ') ? '\0' : c; + } + callsign[CallsignSize-1] = '\0'; + + ssid_ = (*ptr >> 1) & 0x0f; + call_ = String((char*)callsign); + + return true; +} + +} // AX25 diff --git a/ax25_callsign.h b/ax25_callsign.h new file mode 100644 index 0000000..742061a --- /dev/null +++ b/ax25_callsign.h @@ -0,0 +1,42 @@ +#ifndef AX25_CALLSIGN_H +#define AX25_CALLSIGN_H + +#include + +namespace AX25 { + +class Callsign +{ +public: + Callsign(); + Callsign(const Callsign &callsign); + Callsign& operator=(const Callsign &callsign); + + Callsign(const byte *rxPayload, int payloadLength); + Callsign(String inputText); + + inline bool IsValid() const { return isValid_; }; + + String ToString() const; + bool ToBinary(byte *txPayload, int bufferLength) const; + + bool Digirepeat(const String &ownCallsign); + +private: + bool encode(byte *txPtr, int bufferLength) const; + + bool fromString(String callsign); + bool fromBinary(const byte *rxPtr, int payloadLength); + +private: + const int CallsignSize = 7; + +private: + bool isValid_; + String call_; + byte ssid_; +}; + +} // AX25 + +#endif // APRSMSG_H diff --git a/ax25_payload.cpp b/ax25_payload.cpp index c99dd31..d76004c 100644 --- a/ax25_payload.cpp +++ b/ax25_payload.cpp @@ -5,47 +5,47 @@ namespace AX25 { Payload::Payload(byte *rxPayload, int payloadLength) : rptCallsCount_(0) { - isValid_ = parsePayload(rxPayload, payloadLength); + isValid_ = fromBinary(rxPayload, payloadLength); } Payload::Payload(String inputText) : rptCallsCount_(0) { - isValid_ = parseString(inputText); + isValid_ = fromString(inputText); } void Payload::Dump() { Serial.println(); Serial.print("valid: "); Serial.println(isValid_); - Serial.println("src: " + srcCall_); - Serial.println("dst: " + dstCall_); + Serial.println("src: " + srcCall_.ToString()); + Serial.println("dst: " + dstCall_.ToString()); Serial.print("rpt: "); for (int i = 0; i < rptCallsCount_; i++) { - Serial.print(rptCalls_[i] + " "); + Serial.print(rptCalls_[i].ToString() + " "); } Serial.println(); Serial.println("info: " + info_); } -int Payload::ToBinary(byte *txPayload, int bufferLength) +int Payload::ToBinary(byte *txPayload, int bufferLength) const { byte *txPtr = txPayload; byte *txEnd = txPayload + bufferLength; // destination address - if (!encodeCall(dstCall_, txPtr, CallsignSize)) return 0; + if (!dstCall_.ToBinary(txPtr, CallsignSize)) return 0; txPtr += CallsignSize; if (txPtr >= txEnd) return 0; // source address - if (!encodeCall(srcCall_, txPtr, CallsignSize)) return 0; + if (!srcCall_.ToBinary(txPtr, CallsignSize)) return 0; txPtr += CallsignSize; if (txPtr >= txEnd) return 0; // digipeater addresses for (int i = 0; i < rptCallsCount_; i++) { - if (!encodeCall(rptCalls_[i], txPtr, CallsignSize)) return 0; + if (!rptCalls_[i].ToBinary(txPtr, CallsignSize)) return 0; txPtr += CallsignSize; if (txPtr >= txEnd) return 0; } @@ -67,37 +67,49 @@ int Payload::ToBinary(byte *txPayload, int bufferLength) return (int)(txPtr-txPayload); } -String Payload::ToText(String customComment) +String Payload::ToString(String customComment) { - String txt = srcCall_ + String(">") + dstCall_; + String txt = srcCall_.ToString() + String(">") + dstCall_.ToString(); for (int i = 0; i < rptCallsCount_; i++) { - if (rptCalls_[i].length() > 0) { - txt += String(",") + rptCalls_[i]; + if (rptCalls_[i].IsValid()) { + txt += String(",") + rptCalls_[i].ToString(); } } - + txt += String(":") + info_; if (info_.startsWith("=")) { txt += customComment; } - + return txt + String("\n"); } -bool Payload::parsePayload(const byte *rxPayload, int payloadLength) +bool Payload::Digirepeat(const String &ownCallsign) +{ + for (int i = 0; i < rptCallsCount_; i++) { + if (rptCalls_[i].Digirepeat(ownCallsign)) { + return true; + } + } + return false; +} + +bool Payload::fromBinary(const byte *rxPayload, int payloadLength) { const byte *rxPtr = rxPayload; const byte *rxEnd = rxPayload + payloadLength; - + + if (payloadLength < CallsignSize) return false; + // destination address - dstCall_ = decodeCall(rxPtr); + dstCall_ = AX25::Callsign(rxPtr, CallsignSize); rxPtr += CallsignSize; if (rxPtr >= rxEnd) return false; // source address - srcCall_ = decodeCall(rxPtr); + srcCall_ = AX25::Callsign(rxPtr, CallsignSize); rxPtr += CallsignSize; if (rxPtr >= rxEnd) return false; @@ -106,7 +118,7 @@ bool Payload::parsePayload(const byte *rxPayload, int payloadLength) // digipeater addresses for (int i = 0; i < RptMaxCount; i++) { if ((rxPayload[(i + 2) * CallsignSize - 1] & 1) == 0) { - rptCalls_[i] = decodeCall(rxPtr); + rptCalls_[i] = AX25::Callsign(rxPtr, CallsignSize); rptCallsCount_++; rxPtr += CallsignSize; if (rxPtr >= rxEnd) return false; @@ -129,7 +141,7 @@ bool Payload::parsePayload(const byte *rxPayload, int payloadLength) return true; } -bool Payload::parseString(String inputText) +bool Payload::fromString(String inputText) { int rptIndex = inputText.indexOf('>'); int infoIndex = inputText.indexOf(':'); @@ -160,60 +172,4 @@ bool Payload::parseString(String inputText) return true; } -bool Payload::encodeCall(String inputText, byte *txPtr, int bufferLength) -{ - if (bufferLength < CallsignSize) return false; - - String callsign = inputText; - byte ssid = 0; - - int delimIndex = inputText.indexOf('-'); - if (delimIndex + 1 >= inputText.length()) return false; - - if (delimIndex != -1) { - callsign = inputText.substring(0, delimIndex); - ssid = inputText.substring(delimIndex + 1).toInt(); - } - - byte *ptr = txPtr; - - memset(ptr, 0, bufferLength); - - for (int i = 0; i < CallsignSize - 1; i++) { - if (i < callsign.length()) { - char c = callsign.charAt(i); - *(ptr++) = c << 1; - } - else { - *(ptr++) = char(' ') << 1; - } - } - *(txPtr + CallsignSize - 1) = ssid << 1; - - return true; -} - -String Payload::decodeCall(const byte *rxPtr) -{ - byte callsign[CallsignSize]; - - const byte *ptr = rxPtr; - - memset(callsign, 0, sizeof(callsign)); - - for (int i = 0; i < CallsignSize - 1; i++) { - char c = *(ptr++) >> 1; - callsign[i] = (c == ' ') ? '\0' : c; - } - callsign[CallsignSize-1] = '\0'; - byte ssid = (*ptr >> 1) & 0x0f; - - String result = String((char*)callsign); - - if (result.length() > 0 && ssid != 0) { - result += String("-") + String(ssid); - } - return result; -} - } // AX25 diff --git a/ax25_payload.h b/ax25_payload.h index 37145de..ace98d4 100644 --- a/ax25_payload.h +++ b/ax25_payload.h @@ -1,8 +1,10 @@ -#ifndef APRSMSG_H -#define APRSMSG_H +#ifndef AX25_PAYLOAD_H +#define AX25_PAYLOAD_H #include +#include "ax25_callsign.h" + namespace AX25 { class Payload @@ -13,17 +15,15 @@ public: inline bool IsValid() const { return isValid_; } - String ToText(String customComment); - int ToBinary(byte *txPayload, int bufferLength); + String ToString(String customComment); + int ToBinary(byte *txPayload, int bufferLength) const; + bool Digirepeat(const String &ownCallsign); void Dump(); private: - String decodeCall(const byte *rxPtr); - bool encodeCall(String callsign, byte *txPtr, int bufferLength); - - bool parseString(String inputText); - bool parsePayload(const byte *rxPayload, int payloadLength); + bool fromString(String inputText); + bool fromBinary(const byte *rxPayload, int payloadLength); private: enum AX25Ctrl { @@ -39,8 +39,8 @@ private: private: bool isValid_; - String srcCall_, dstCall_; - String rptCalls_[7]; + Callsign srcCall_, dstCall_; + Callsign rptCalls_[7]; int rptCallsCount_; String info_; }; diff --git a/esp32_loraprs.ino b/esp32_loraprs.ino index b935ba3..0221c8e 100644 --- a/esp32_loraprs.ino +++ b/esp32_loraprs.ino @@ -34,6 +34,7 @@ void initializeConfig() { cfg.EnableSignalReport = true; cfg.EnableAutoFreqCorrection = true; cfg.EnablePersistentAprsConnection = true; + cfg.EnableRfToIs = true; cfg.EnableIsToRf = false; cfg.EnableRepeater = false; } diff --git a/loraprs.cpp b/loraprs.cpp index a76eca6..a6d7e38 100644 --- a/loraprs.cpp +++ b/loraprs.cpp @@ -11,6 +11,7 @@ void LoraPrs::setup(const LoraPrsConfig &conf) { isClient_ = conf.IsClientMode; loraFreq_ = conf.LoraFreq; + ownCallsign_ = conf.AprsLogin; aprsLogin_ = String("user ") + conf.AprsLogin + String(" pass ") + conf.AprsPass + String(" vers ") + CfgLoraprsVersion; @@ -25,6 +26,7 @@ void LoraPrs::setup(const LoraPrsConfig &conf) autoCorrectFreq_ = conf.EnableAutoFreqCorrection; addSignalReport_ = conf.EnableSignalReport; persistentConn_ = conf.EnablePersistentAprsConnection; + enableRfToIs_ = conf.EnableRfToIs; enableIsToRf_ = conf.EnableIsToRf; enableRepeater_ = conf.EnableRepeater; @@ -140,12 +142,8 @@ void LoraPrs::loop() delay(10); } -void LoraPrs::onRfAprsReceived(String aprsMessage) +void LoraPrs::sendToAprsis(String aprsMessage) { - Serial.print(aprsMessage); - - if (isClient_) return; - if (WiFi.status() != WL_CONNECTED) { reconnectWifi(); } @@ -173,26 +171,30 @@ void LoraPrs::onAprsisDataAvailable() if (enableIsToRf_ && aprsisData.length() > 0) { AX25::Payload payload(aprsisData); - - if (payload.IsValid()) { - - byte buf[512]; - int bytesWritten = payload.ToBinary(buf, sizeof(buf)); - if (bytesWritten > 0) { - LoRa.beginPacket(); - LoRa.write(buf, bytesWritten); - LoRa.endPacket(true); - } - else { - Serial.println("Failed to serialize payload"); - } - } - else { - Serial.println("Invalid payload from APRSIS"); - } + sendToLora(payload); } } +bool LoraPrs::sendToLora(const AX25::Payload &payload) +{ + if (!payload.IsValid()) { + Serial.println("Invalid payload from APRSIS"); + return false; + } + + byte buf[512]; + int bytesWritten = payload.ToBinary(buf, sizeof(buf)); + if (bytesWritten <= 0) { + Serial.println("Failed to serialize payload"); + return false; + + } + LoRa.beginPacket(); + LoRa.write(buf, bytesWritten); + LoRa.endPacket(true); + return true; +} + void LoraPrs::onLoraDataAvailable(int packetSize) { int rxBufIndex = 0; @@ -243,7 +245,15 @@ void LoraPrs::onLoraDataAvailable(int packetSize) AX25::Payload payload(rxBuf, rxBufIndex); if (payload.IsValid()) { - onRfAprsReceived(payload.ToText(addSignalReport_ ? signalReport : String())); + String textPayload = payload.ToString(addSignalReport_ ? signalReport : String()); + Serial.print(textPayload); + + if (enableRfToIs_ && !isClient_) { + sendToAprsis(textPayload); + } + if (enableRepeater_ && payload.Digirepeat(ownCallsign_)) { + sendToLora(payload); + } } else { Serial.println("Invalid payload from LoRA"); diff --git a/loraprs.h b/loraprs.h index 1f18864..3f4a0fd 100644 --- a/loraprs.h +++ b/loraprs.h @@ -34,6 +34,7 @@ struct LoraPrsConfig bool EnableSignalReport; bool EnableAutoFreqCorrection; bool EnablePersistentAprsConnection; + bool EnableRfToIs; bool EnableIsToRf; bool EnableRepeater; }; @@ -58,7 +59,8 @@ private: void onBtDataAvailable(); void onAprsisDataAvailable(); - void onRfAprsReceived(String aprsMessage); + void sendToAprsis(String aprsMessage); + bool sendToLora(const AX25::Payload &payload); void kissResetState(); @@ -83,6 +85,7 @@ private: }; const String CfgLoraprsVersion = "LoRAPRS 0.1"; + const String CfgAprsSoftware = "APZLRA"; const byte CfgPinSs = 5; const byte CfgPinRst = 26; @@ -92,6 +95,8 @@ private: bool isClient_; long loraFreq_; + String ownCallsign_; + String aprsHost_; int aprsPort_; String aprsLogin_; @@ -99,6 +104,7 @@ private: bool autoCorrectFreq_; bool addSignalReport_; bool persistentConn_; + bool enableRfToIs_; bool enableIsToRf_; bool enableRepeater_;