kopia lustrzana https://github.com/sh123/esp32_loraprs
Implement IS to RF gating (#6)
* Add aprs string parsing * Added method for conversion to binary ax25 packet * Check if message is valid before forwarding to APRS-IS * Added APRIS data sending into RF * Added Lora sending for IS packets * Fixed bugs in packet encoding * Updated readmepull/15/head
rodzic
c88ed243bc
commit
b945b46fbb
|
@ -43,7 +43,7 @@ All work was done on ESP32-WROOM with custom made LoRa shield, if your ESP32 boa
|
||||||
- other useful options are
|
- other useful options are
|
||||||
- `cfg.EnableSignalReport` set to `true` to enable signal report, it will be added as a comment to APRS-IS submitted location
|
- `cfg.EnableSignalReport` set to `true` to enable signal report, it will be added as a comment to APRS-IS submitted location
|
||||||
- `cfg.EnablePersistentAprsConnection` set to `false` to avoid keeping connection open to APRS-IS
|
- `cfg.EnablePersistentAprsConnection` set to `false` to avoid keeping connection open to APRS-IS
|
||||||
- **[INACTIVE YET]** `cfg.EnableIsToRf` set to `true` to forward APRS-IS traffic to RF
|
- `cfg.EnableIsToRf` set to `true` to forward APRS-IS traffic to RF, see also `cfg.AprsFilter` for traffic filtering
|
||||||
- **[INACTIVE YET]** `cfg.EnableRepeater` set to `true` to enable packet repeater
|
- **[INACTIVE YET]** `cfg.EnableRepeater` set to `true` to enable packet repeater
|
||||||
|
|
||||||
# Test Results
|
# Test Results
|
||||||
|
|
149
ax25_payload.cpp
149
ax25_payload.cpp
|
@ -3,11 +3,71 @@
|
||||||
namespace AX25 {
|
namespace AX25 {
|
||||||
|
|
||||||
Payload::Payload(byte *rxPayload, int payloadLength)
|
Payload::Payload(byte *rxPayload, int payloadLength)
|
||||||
|
: rptCallsCount_(0)
|
||||||
{
|
{
|
||||||
parsePayload(rxPayload, payloadLength);
|
isValid_ = parsePayload(rxPayload, payloadLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
String Payload::ToText(const String &customComment)
|
Payload::Payload(String inputText)
|
||||||
|
: rptCallsCount_(0)
|
||||||
|
{
|
||||||
|
isValid_ = parseString(inputText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Payload::Dump()
|
||||||
|
{
|
||||||
|
Serial.println();
|
||||||
|
Serial.print("valid: "); Serial.println(isValid_);
|
||||||
|
Serial.println("src: " + srcCall_);
|
||||||
|
Serial.println("dst: " + dstCall_);
|
||||||
|
Serial.print("rpt: ");
|
||||||
|
for (int i = 0; i < rptCallsCount_; i++) {
|
||||||
|
Serial.print(rptCalls_[i] + " ");
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("info: " + info_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Payload::ToBinary(byte *txPayload, int bufferLength)
|
||||||
|
{
|
||||||
|
byte *txPtr = txPayload;
|
||||||
|
byte *txEnd = txPayload + bufferLength;
|
||||||
|
|
||||||
|
// destination address
|
||||||
|
if (!encodeCall(dstCall_, txPtr, CallsignSize)) return 0;
|
||||||
|
txPtr += CallsignSize;
|
||||||
|
if (txPtr >= txEnd) return 0;
|
||||||
|
|
||||||
|
// source address
|
||||||
|
if (!encodeCall(srcCall_, 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;
|
||||||
|
txPtr += CallsignSize;
|
||||||
|
if (txPtr >= txEnd) return 0;
|
||||||
|
}
|
||||||
|
*(txPtr - 1) |= 1;
|
||||||
|
|
||||||
|
// control + protocol id
|
||||||
|
if ((txPtr + 2) >= txEnd) return 0;
|
||||||
|
*(txPtr++) = AX25Ctrl::UI;
|
||||||
|
*(txPtr++) = AX25Pid::NoLayer3;
|
||||||
|
|
||||||
|
// information field
|
||||||
|
for (int i = 0; i < info_.length(); i++) {
|
||||||
|
char c = info_.charAt(i);
|
||||||
|
if (c == '\0') break;
|
||||||
|
*(txPtr++) = c;
|
||||||
|
if (txPtr >= txEnd) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(txPtr-txPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
String Payload::ToText(String customComment)
|
||||||
{
|
{
|
||||||
String txt = srcCall_ + String(">") + dstCall_;
|
String txt = srcCall_ + String(">") + dstCall_;
|
||||||
|
|
||||||
|
@ -26,15 +86,15 @@ String Payload::ToText(const String &customComment)
|
||||||
return txt + String("\n");
|
return txt + String("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Payload::parsePayload(byte *rxPayload, int payloadLength)
|
bool Payload::parsePayload(const byte *rxPayload, int payloadLength)
|
||||||
{
|
{
|
||||||
byte *rxPtr = rxPayload;
|
const byte *rxPtr = rxPayload;
|
||||||
byte *rxEnd = rxPayload + payloadLength;
|
const byte *rxEnd = rxPayload + payloadLength;
|
||||||
|
|
||||||
// destination address
|
// destination address
|
||||||
dstCall_ = decodeCall(rxPtr);
|
dstCall_ = decodeCall(rxPtr);
|
||||||
rxPtr += CallsignSize;
|
rxPtr += CallsignSize;
|
||||||
if (rxPtr >= rxPayload + payloadLength) return false;
|
if (rxPtr >= rxEnd) return false;
|
||||||
|
|
||||||
// source address
|
// source address
|
||||||
srcCall_ = decodeCall(rxPtr);
|
srcCall_ = decodeCall(rxPtr);
|
||||||
|
@ -42,7 +102,7 @@ bool Payload::parsePayload(byte *rxPayload, int payloadLength)
|
||||||
if (rxPtr >= rxEnd) return false;
|
if (rxPtr >= rxEnd) return false;
|
||||||
|
|
||||||
rptCallsCount_ = 0;
|
rptCallsCount_ = 0;
|
||||||
|
|
||||||
// digipeater addresses
|
// digipeater addresses
|
||||||
for (int i = 0; i < RptMaxCount; i++) {
|
for (int i = 0; i < RptMaxCount; i++) {
|
||||||
if ((rxPayload[(i + 2) * CallsignSize - 1] & 1) == 0) {
|
if ((rxPayload[(i + 2) * CallsignSize - 1] & 1) == 0) {
|
||||||
|
@ -69,25 +129,88 @@ bool Payload::parsePayload(byte *rxPayload, int payloadLength)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Payload::decodeCall(byte *rxPtr)
|
bool Payload::parseString(String inputText)
|
||||||
|
{
|
||||||
|
int rptIndex = inputText.indexOf('>');
|
||||||
|
int infoIndex = inputText.indexOf(':');
|
||||||
|
|
||||||
|
// bad format
|
||||||
|
if (rptIndex == -1 || infoIndex == -1) return false;
|
||||||
|
|
||||||
|
info_ = inputText.substring(infoIndex + 1);
|
||||||
|
srcCall_ = inputText.substring(0, rptIndex);
|
||||||
|
String paths = inputText.substring(rptIndex + 1, infoIndex);
|
||||||
|
|
||||||
|
rptCallsCount_ = 0;
|
||||||
|
int index = 0;
|
||||||
|
while (rptCallsCount_ <= RptMaxCount) {
|
||||||
|
int nextIndex = paths.indexOf(',', index);
|
||||||
|
String pathItem = paths.substring(index, nextIndex == -1 ? paths.length() : nextIndex);
|
||||||
|
if (index == 0) {
|
||||||
|
dstCall_ = pathItem;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rptCallsCount_++;
|
||||||
|
rptCalls_[rptCallsCount_ - 1] = pathItem;
|
||||||
|
}
|
||||||
|
if (nextIndex == -1) break;
|
||||||
|
index = nextIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
byte callsign[CallsignSize];
|
||||||
char ssid;
|
|
||||||
|
|
||||||
byte *ptr = rxPtr;
|
const byte *ptr = rxPtr;
|
||||||
|
|
||||||
memset(callsign, 0, sizeof(callsign));
|
memset(callsign, 0, sizeof(callsign));
|
||||||
|
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < CallsignSize - 1; i++) {
|
||||||
char c = *(ptr++) >> 1;
|
char c = *(ptr++) >> 1;
|
||||||
callsign[i] = (c == ' ') ? '\0' : c;
|
callsign[i] = (c == ' ') ? '\0' : c;
|
||||||
}
|
}
|
||||||
callsign[CallsignSize-1] = '\0';
|
callsign[CallsignSize-1] = '\0';
|
||||||
ssid = (*ptr >> 1);
|
byte ssid = (*ptr >> 1) & 0x0f;
|
||||||
|
|
||||||
String result = String((char*)callsign);
|
String result = String((char*)callsign);
|
||||||
|
|
||||||
if (result.length() > 0 && ssid >= '0' && ssid <= '9') {
|
if (result.length() > 0 && ssid != 0) {
|
||||||
result += String("-") + String(ssid);
|
result += String("-") + String(ssid);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -9,11 +9,21 @@ class Payload
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Payload(byte *rxPayload, int payloadLength);
|
Payload(byte *rxPayload, int payloadLength);
|
||||||
String ToText(const String &customComment);
|
Payload(String inputText);
|
||||||
|
|
||||||
|
inline bool IsValid() const { return isValid_; }
|
||||||
|
|
||||||
|
String ToText(String customComment);
|
||||||
|
int ToBinary(byte *txPayload, int bufferLength);
|
||||||
|
|
||||||
|
void Dump();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String decodeCall(byte *rxPtr);
|
String decodeCall(const byte *rxPtr);
|
||||||
bool parsePayload(byte *rxPayload, int payloadLength);
|
bool encodeCall(String callsign, byte *txPtr, int bufferLength);
|
||||||
|
|
||||||
|
bool parseString(String inputText);
|
||||||
|
bool parsePayload(const byte *rxPayload, int payloadLength);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum AX25Ctrl {
|
enum AX25Ctrl {
|
||||||
|
@ -28,6 +38,7 @@ private:
|
||||||
const int RptMaxCount = 7;
|
const int RptMaxCount = 7;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool isValid_;
|
||||||
String srcCall_, dstCall_;
|
String srcCall_, dstCall_;
|
||||||
String rptCalls_[7];
|
String rptCalls_[7];
|
||||||
int rptCallsCount_;
|
int rptCallsCount_;
|
||||||
|
|
69
loraprs.cpp
69
loraprs.cpp
|
@ -4,7 +4,7 @@ LoraPrs::LoraPrs()
|
||||||
: serialBt_()
|
: serialBt_()
|
||||||
, kissState_(KissState::Void)
|
, kissState_(KissState::Void)
|
||||||
, kissCmd_(KissCmd::NoCmd)
|
, kissCmd_(KissCmd::NoCmd)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoraPrs::setup(const LoraPrsConfig &conf)
|
void LoraPrs::setup(const LoraPrsConfig &conf)
|
||||||
|
@ -38,7 +38,7 @@ void LoraPrs::setup(const LoraPrsConfig &conf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoraPrs::setupWifi(const String &wifiName, const String &wifiKey)
|
void LoraPrs::setupWifi(const String &wifiName, const String &wifiKey)
|
||||||
{
|
{
|
||||||
if (!isClient_) {
|
if (!isClient_) {
|
||||||
Serial.print("WIFI connecting to " + wifiName);
|
Serial.print("WIFI connecting to " + wifiName);
|
||||||
|
@ -56,8 +56,8 @@ void LoraPrs::setupWifi(const String &wifiName, const String &wifiKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoraPrs::reconnectWifi() {
|
void LoraPrs::reconnectWifi()
|
||||||
|
{
|
||||||
Serial.print("WIFI re-connecting...");
|
Serial.print("WIFI re-connecting...");
|
||||||
|
|
||||||
while (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0,0,0,0)) {
|
while (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0,0,0,0)) {
|
||||||
|
@ -69,8 +69,8 @@ void LoraPrs::reconnectWifi() {
|
||||||
Serial.println("ok");
|
Serial.println("ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraPrs::reconnectAprsis() {
|
bool LoraPrs::reconnectAprsis()
|
||||||
|
{
|
||||||
Serial.print("APRSIS connecting...");
|
Serial.print("APRSIS connecting...");
|
||||||
|
|
||||||
if (!aprsisConn_.connect(aprsHost_.c_str(), aprsPort_)) {
|
if (!aprsisConn_.connect(aprsHost_.c_str(), aprsPort_)) {
|
||||||
|
@ -78,7 +78,7 @@ bool LoraPrs::reconnectAprsis() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.println("ok");
|
Serial.println("ok");
|
||||||
|
|
||||||
aprsisConn_.print(aprsLogin_);
|
aprsisConn_.print(aprsLogin_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -127,9 +127,8 @@ void LoraPrs::loop()
|
||||||
if (!aprsisConn_.connected()) {
|
if (!aprsisConn_.connected()) {
|
||||||
reconnectAprsis();
|
reconnectAprsis();
|
||||||
}
|
}
|
||||||
while (aprsisConn_.available() > 0) {
|
if (aprsisConn_.available() > 0) {
|
||||||
char c = aprsisConn_.read();
|
onAprsisDataAvailable();
|
||||||
Serial.print(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (serialBt_.available()) {
|
if (serialBt_.available()) {
|
||||||
|
@ -141,17 +140,18 @@ void LoraPrs::loop()
|
||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoraPrs::onRfAprsReceived(const String &aprsMessage)
|
void LoraPrs::onRfAprsReceived(String aprsMessage)
|
||||||
{
|
{
|
||||||
|
Serial.print(aprsMessage);
|
||||||
|
|
||||||
if (isClient_) return;
|
if (isClient_) return;
|
||||||
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
reconnectWifi();
|
reconnectWifi();
|
||||||
}
|
}
|
||||||
if (!aprsisConn_.connected()) {
|
if (!aprsisConn_.connected()) {
|
||||||
reconnectAprsis();
|
reconnectAprsis();
|
||||||
}
|
}
|
||||||
Serial.print(aprsMessage);
|
|
||||||
aprsisConn_.print(aprsMessage);
|
aprsisConn_.print(aprsMessage);
|
||||||
|
|
||||||
if (!persistentConn_) {
|
if (!persistentConn_) {
|
||||||
|
@ -159,6 +159,40 @@ void LoraPrs::onRfAprsReceived(const String &aprsMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoraPrs::onAprsisDataAvailable()
|
||||||
|
{
|
||||||
|
String aprsisData;
|
||||||
|
|
||||||
|
while (aprsisConn_.available() > 0) {
|
||||||
|
char c = aprsisConn_.read();
|
||||||
|
if (c == '\r') continue;
|
||||||
|
Serial.print(c);
|
||||||
|
if (c == '\n') break;
|
||||||
|
aprsisData += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LoraPrs::onLoraDataAvailable(int packetSize)
|
void LoraPrs::onLoraDataAvailable(int packetSize)
|
||||||
{
|
{
|
||||||
int rxBufIndex = 0;
|
int rxBufIndex = 0;
|
||||||
|
@ -206,10 +240,13 @@ void LoraPrs::onLoraDataAvailable(int packetSize)
|
||||||
LoRa.setFrequency(loraFreq_);
|
LoRa.setFrequency(loraFreq_);
|
||||||
}
|
}
|
||||||
|
|
||||||
String aprsMsg = AX25::Payload(rxBuf, rxBufIndex).ToText(addSignalReport_ ? signalReport : String());
|
AX25::Payload payload(rxBuf, rxBufIndex);
|
||||||
|
|
||||||
if (aprsMsg.length() != 0) {
|
if (payload.IsValid()) {
|
||||||
onRfAprsReceived(aprsMsg);
|
onRfAprsReceived(payload.ToText(addSignalReport_ ? signalReport : String()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Serial.println("Invalid payload from LoRA");
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(50);
|
delay(50);
|
||||||
|
|
|
@ -56,8 +56,9 @@ private:
|
||||||
|
|
||||||
void onLoraDataAvailable(int packetSize);
|
void onLoraDataAvailable(int packetSize);
|
||||||
void onBtDataAvailable();
|
void onBtDataAvailable();
|
||||||
|
void onAprsisDataAvailable();
|
||||||
|
|
||||||
void onRfAprsReceived(const String &aprsMessage);
|
void onRfAprsReceived(String aprsMessage);
|
||||||
|
|
||||||
void kissResetState();
|
void kissResetState();
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue