diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8da3c3d..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 sh123 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 57cfc17..4bffe5c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Can be used in two modes: - Arduino Timer library: https://github.com/contrem/arduino-timer # Software Setup -- when setting up APRSDroid, use **"TNC (plaintext TNC2)"** connection protocol in Connection Preferences -> Connection Protocol +- when setting up APRSDroid, use **"TNC (KISS)"** connection protocol in Connection Preferences -> Connection Protocol - go to esp32_loraprs.ino and make next changes based on your requirements - comment out / remove **LORAPRS_CLIENT** define if you are planning to run server mode for APRS-IS iGate - for server mode fill **LORAPRS_WIFI_SSID** and **LORAPRS_WIFI_KEY** with your WiFI AP data diff --git a/esp32_loraprs.ino b/esp32_loraprs.ino index 423d8c9..10b0b37 100644 --- a/esp32_loraprs.ino +++ b/esp32_loraprs.ino @@ -7,7 +7,11 @@ #define LORAPRS_CLIENT +#ifdef LORAPRS_CLIENT +#define LORAPRS_FREQ 432.499E6 +#else #define LORAPRS_FREQ 432.5E6 +#endif #ifdef LORAPRS_CLIENT #define LORAPRS_BT_NAME "loraprs_client" diff --git a/loraprs.cpp b/loraprs.cpp index 6adb054..27ecc64 100644 --- a/loraprs.cpp +++ b/loraprs.cpp @@ -1,12 +1,14 @@ #include "loraprs.h" -LoraPrs::LoraPrs(int loraFreq, String btName, String wifiName, - String wifiKey, String aprsLoginCallsign, String aprsPass) +LoraPrs::LoraPrs(int loraFreq, const String & btName, const String & wifiName, + const String & wifiKey, const String & aprsLoginCallsign, const String & aprsPass) : serialBt_() , loraFreq_(loraFreq) , btName_(btName) , wifiName_(wifiName) , wifiKey_(wifiKey) + , kissState_(KissState::Void) + , kissCmd_(KissCmd::NoCmd) { aprsLogin_ = ""; aprsLogin_ += "user "; @@ -25,7 +27,7 @@ void LoraPrs::setup() setupBt(btName_); } -void LoraPrs::setupWifi(String wifiName, String wifiKey) +void LoraPrs::setupWifi(const String & wifiName, const String & wifiKey) { if (wifiName.length() != 0) { Serial.print("WIFI connecting to " + wifiName); @@ -71,11 +73,12 @@ void LoraPrs::setupLora(int loraFreq) LoRa.setSignalBandwidth(CfgBw); LoRa.setCodingRate4(CfgCodingRate); LoRa.setTxPower(CfgPower); + LoRa.enableCrc(); Serial.println("ok"); } -void LoraPrs::setupBt(String btName) +void LoraPrs::setupBt(const String & btName) { if (btName.length() != 0) { Serial.print("BT init " + btName + "..."); @@ -98,16 +101,14 @@ void LoraPrs::loop() if (serialBt_.available()) { onBtReceived(); } - if (LoRa.parsePacket()) { - onLoraReceived(); + if (int packetSize = LoRa.parsePacket()) { + onLoraReceived(packetSize); } delay(10); } -void LoraPrs::onAprsReceived(String aprsMessage) +void LoraPrs::onAprsReceived(const String & aprsMessage) { - Serial.print(aprsMessage); - if (WiFi.status() == WL_CONNECTED) { WiFiClient wifiClient; @@ -116,35 +117,196 @@ void LoraPrs::onAprsReceived(String aprsMessage) return; } wifiClient.print(aprsLogin_); + Serial.print(aprsMessage); wifiClient.print(aprsMessage); wifiClient.stop(); + } + else { + Serial.println("Wifi not connected, not sent"); } } -void LoraPrs::onLoraReceived() +String LoraPrs::decodeCall(byte *rxPtr) { - String buf; + byte callsign[7]; + char ssid; + + byte *ptr = rxPtr; + + memset(callsign, 0, sizeof(callsign)); + + for (int i = 0; i < 6; i++) { + char c = *(ptr++) >> 1; + callsign[i] = (c == ' ') ? '\0' : c; + } + callsign[6] = '\0'; + ssid = (*ptr >> 1); + + String result = String((char*)callsign); + if (ssid >= '0' && ssid <= '9') { + result += String("-") + String(ssid); + } + return result; +} + +String LoraPrs::convertAX25ToAprs(byte *rxPayload, int payloadLength, const String & signalReport) +{ + byte *rxPtr = rxPayload; + String srcCall, dstCall, rptFirst, rptSecond, result; + + dstCall = decodeCall(rxPtr); + rxPtr += 7; + + srcCall = decodeCall(rxPtr); + rxPtr += 7; + + if ((rxPayload[13] & 1) == 0) { + rptFirst = decodeCall(rxPtr); + rxPtr += 7; + + if ((rxPayload[20] & 1) == 0) { + rptSecond = decodeCall(rxPtr); + rxPtr += 7; + } + } + + if (*(rxPtr++) != AX25Ctrl::UI) return result; + if (*(rxPtr++) != AX25Pid::NoLayer3) return result; + + result += srcCall + String(">") + dstCall; + + if (rptFirst.length() > 0) { + result += String(",") + rptFirst; + } + if (rptSecond.length() > 0) { + result += String(",") + rptSecond; + } + + result += ":"; + + bool appendReport = ((char)*rxPtr == '='); + + while (rxPtr < rxPayload + payloadLength) { + result += String((char)*(rxPtr++)); + } + + if (appendReport) { + result += signalReport; + } + else { + result += "\n"; + } + + return result; +} + +void LoraPrs::onLoraReceived(int packetSize) +{ + int rxBufIndex = 0; + byte rxBuf[packetSize]; + + serialBt_.write(KissMarker::Fend); + serialBt_.write(KissCmd::Data); + while (LoRa.available()) { - char c = (char)LoRa.read(); - if (c != '\n') - buf += c; + byte rxByte = LoRa.read(); + + if (rxByte == KissMarker::Fend) { + serialBt_.write(KissMarker::Fesc); + serialBt_.write(KissMarker::Tfend); + } + else if (rxByte == KissMarker::Fesc) { + serialBt_.write(KissMarker::Fesc); + serialBt_.write(KissMarker::Tfesc); + } + else { + rxBuf[rxBufIndex++] = rxByte; + serialBt_.write(rxByte); + } } - for (int i; i < buf.length(); i++) { - serialBt_.write((uint8_t)buf[i]); + + serialBt_.write(KissMarker::Fend); + + String signalReport = String(" ") + + String("RSSI: ") + + String(LoRa.packetRssi()) + + String(", ") + + String("SNR: ") + + String(LoRa.packetSnr()) + + String("dB, ") + + String("ERR: ") + + String(LoRa.packetFrequencyError()) + + String("Hz\n"); + + String aprsMsg = convertAX25ToAprs(rxBuf, rxBufIndex, signalReport); + + if (aprsMsg.length() != 0) { + onAprsReceived(aprsMsg); } - onAprsReceived(buf + " " + - "RSSI: " + String(LoRa.packetRssi()) + ", " + - "SNR: " + String(LoRa.packetSnr()) + "dB, " + - "ERR: " + String(LoRa.packetFrequencyError()) + "Hz\n"); + delay(50); } -void LoraPrs::onBtReceived() +void LoraPrs::kissResetState() { - LoRa.beginPacket(); - while (serialBt_.available()) { - char c = (char)serialBt_.read(); - LoRa.print(c); - } - LoRa.endPacket(); + kissCmd_ = KissCmd::NoCmd; + kissState_ = KissState::Void; +} + +void LoraPrs::onBtReceived() +{ + while (serialBt_.available()) { + byte txByte = serialBt_.read(); + + switch (kissState_) { + case KissState::Void: + if (txByte == KissMarker::Fend) { + kissCmd_ = KissCmd::NoCmd; + kissState_ = KissState::GetCmd; + } + break; + case KissState::GetCmd: + if (txByte != KissMarker::Fend) { + if (txByte == KissCmd::Data) { + LoRa.beginPacket(); + kissCmd_ = (KissCmd)txByte; + kissState_ = KissState::GetData; + } + else { + kissResetState(); + } + } + break; + case KissState::GetData: + if (txByte == KissMarker::Fesc) { + kissState_ = KissState::Escape; + } + else if (txByte == KissMarker::Fend) { + if (kissCmd_ == KissCmd::Data) { + LoRa.endPacket(); + } + kissResetState(); + } + else { + LoRa.write(txByte); + } + break; + case KissState::Escape: + if (txByte == KissMarker::Tfend) { + LoRa.write(KissMarker::Fend); + kissState_ = KissState::GetData; + } + else if (txByte == KissMarker::Tfesc) { + LoRa.write(KissMarker::Fesc); + kissState_ = KissState::GetData; + } + else { + kissResetState(); + } + break; + default: + break; + } + } + delay(20); } diff --git a/loraprs.h b/loraprs.h index a0e32e7..101c8d1 100644 --- a/loraprs.h +++ b/loraprs.h @@ -11,6 +11,60 @@ class LoraPrs { public: + LoraPrs(int loraFreq, const String & btName, const String & wifiName, + const String & wifiKey, const String & aprsLoginCallsign, const String & aprsPass); + + void setup(); + void loop(); + +private: + void setupWifi(const String & wifiName, const String & wifiKey); + void setupLora(int loraFreq); + void setupBt(const String & btName); + + void reconnectWifi(); + + void onLoraReceived(int packetSize); + void onBtReceived(); + + void kissResetState(); + + void onAprsReceived(const String & aprsMessage); + + bool isAX25CrcValid(byte *rxPayload, int payloadLength); + String convertAX25ToAprs(byte *rxPayload, int payloadLength, const String & signalReport); + String decodeCall(byte *rxPtr); + + uint16_t updateCrcCcit(uint8_t newByte, uint16_t prevCrc); + +private: + enum KissMarker { + Fend = 0xc0, + Fesc = 0xdb, + Tfend = 0xdc, + Tfesc = 0xdd + }; + + enum KissState { + Void = 0, + GetCmd, + GetData, + Escape + }; + + enum KissCmd { + Data = 0x00, + NoCmd = 0x80 + }; + + enum AX25Ctrl { + UI = 0x03 + }; + + enum AX25Pid { + NoLayer3 = 0xf0 + }; + const String CfgLoraprsVersion = "LoRAPRS 0.1"; const byte CfgPinSs = 5; @@ -26,32 +80,17 @@ public: const int CfgAprsPort = 14580; const String CfgAprsHost = "rotate.aprs2.net"; -public: - LoraPrs(int loraFreq, String btName, String wifiName, - String wifiKey, String aprsLoginCallsign, String aprsPass); - - void setup(); - void loop(); - private: - void setupWifi(String wifiName, String wifiKey); - void setupLora(int loraFreq); - void setupBt(String btName); - - void reconnectWifi(); - - void onLoraReceived(); - void onBtReceived(); - void onAprsReceived(String aprsMessage); - -private: - BluetoothSerial serialBt_; - int loraFreq_; String btName_; String wifiName_; String wifiKey_; String aprsLogin_; + + KissCmd kissCmd_; + KissState kissState_; + + BluetoothSerial serialBt_; }; #endif // LORAPRS_H