mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-03-17 10:44:47 +01:00
feat: tnc server
This commit is contained in:
parent
6bd6cce82b
commit
2a5cc33711
|
|
@ -14,6 +14,7 @@
|
|||
#include "gps_utils.h"
|
||||
#include "bme_utils.h"
|
||||
#include "web_utils.h"
|
||||
#include "tnc_utils.h"
|
||||
#include "display.h"
|
||||
#include "utils.h"
|
||||
#include <ElegantOTA.h>
|
||||
|
|
@ -82,6 +83,7 @@ void setup() {
|
|||
SYSLOG_Utils::setup();
|
||||
BME_Utils::setup();
|
||||
WEB_Utils::setup();
|
||||
TNC_Utils::setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
|
@ -101,6 +103,8 @@ void loop() {
|
|||
APRS_IS_Utils::connect();
|
||||
}
|
||||
|
||||
TNC_Utils::loop();
|
||||
|
||||
Utils::checkDisplayInterval();
|
||||
Utils::checkBeaconInterval();
|
||||
|
||||
|
|
@ -112,12 +116,16 @@ void loop() {
|
|||
|
||||
APRS_IS_Utils::checkStatus(); // Need that to update display, maybe split this and send APRSIS status to display func?
|
||||
|
||||
if (Config.aprs_is.active) { // If APRSIS enabled
|
||||
APRS_IS_Utils::loop(packet); // Send received packet to APRSIS
|
||||
}
|
||||
if (packet != "") {
|
||||
if (Config.aprs_is.active) { // If APRSIS enabled
|
||||
APRS_IS_Utils::loop(packet); // Send received packet to APRSIS
|
||||
}
|
||||
|
||||
if (Config.digi.mode == 2) { // If Digi enabled
|
||||
DIGI_Utils::loop(packet); // Send received packet to Digi
|
||||
if (Config.digi.mode == 2) { // If Digi enabled
|
||||
DIGI_Utils::loop(packet); // Send received packet to Digi
|
||||
}
|
||||
|
||||
TNC_Utils::sendToClients(packet); // Send received packet to TNC KISS
|
||||
}
|
||||
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
|
|
|
|||
23
src/kiss_protocol.h
Normal file
23
src/kiss_protocol.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#define DCD_ON 0x03
|
||||
|
||||
#define FEND 0xC0
|
||||
#define FESC 0xDB
|
||||
#define TFEND 0xDC
|
||||
#define TFESC 0xDD
|
||||
|
||||
#define CMD_UNKNOWN 0xFE
|
||||
#define CMD_DATA 0x00
|
||||
#define CMD_HARDWARE 0x06
|
||||
|
||||
#define HW_RSSI 0x21
|
||||
|
||||
#define CMD_ERROR 0x90
|
||||
#define ERROR_INITRADIO 0x01
|
||||
#define ERROR_TXFAILED 0x02
|
||||
#define ERROR_QUEUE_FULL 0x04
|
||||
|
||||
#define APRS_CONTROL_FIELD 0x03
|
||||
#define APRS_INFORMATION_FIELD 0xf0
|
||||
|
||||
#define HAS_BEEN_DIGIPITED_MASK 0b10000000
|
||||
#define IS_LAST_ADDRESS_POSITION_MASK 0b1
|
||||
170
src/kiss_utils.cpp
Normal file
170
src/kiss_utils.cpp
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#include <Arduino.h>
|
||||
#include "kiss_protocol.h"
|
||||
|
||||
bool validateTNC2Frame(const String& tnc2FormattedFrame) {
|
||||
return (tnc2FormattedFrame.indexOf(':') != -1) && (tnc2FormattedFrame.indexOf('>') != -1);
|
||||
}
|
||||
|
||||
bool validateKISSFrame(const String& kissFormattedFrame) {
|
||||
return kissFormattedFrame.charAt(0) == (char)FEND && kissFormattedFrame.charAt(kissFormattedFrame.length() - 1) == (char)FEND;
|
||||
}
|
||||
|
||||
String encodeAddressAX25(String tnc2Address) {
|
||||
bool hasBeenDigipited = tnc2Address.indexOf('*') != -1;
|
||||
|
||||
if (tnc2Address.indexOf('-') == -1) {
|
||||
if (hasBeenDigipited) {
|
||||
tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
|
||||
}
|
||||
|
||||
tnc2Address += "-0";
|
||||
}
|
||||
|
||||
int separatorIndex = tnc2Address.indexOf('-');
|
||||
int ssid = tnc2Address.substring(separatorIndex + 1).toInt();
|
||||
|
||||
String kissAddress = "";
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
char addressChar;
|
||||
if (tnc2Address.length() > i && i < separatorIndex) {
|
||||
addressChar = tnc2Address.charAt(i);
|
||||
} else {
|
||||
addressChar = ' ';
|
||||
}
|
||||
kissAddress += (char)(addressChar << 1);
|
||||
}
|
||||
|
||||
kissAddress += (char)((ssid << 1) | 0b01100000 | (hasBeenDigipited ? HAS_BEEN_DIGIPITED_MASK : 0));
|
||||
return kissAddress;
|
||||
}
|
||||
|
||||
String decodeAddressAX25(const String& ax25Address, bool& isLast, bool isRelay) {
|
||||
String address = "";
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
uint8_t currentCharacter = ax25Address.charAt(i);
|
||||
currentCharacter >>= 1;
|
||||
if (currentCharacter != ' ') {
|
||||
address += (char)currentCharacter;
|
||||
}
|
||||
}
|
||||
auto ssidChar = (uint8_t)ax25Address.charAt(6);
|
||||
bool hasBeenDigipited = ssidChar & HAS_BEEN_DIGIPITED_MASK;
|
||||
isLast = ssidChar & IS_LAST_ADDRESS_POSITION_MASK;
|
||||
ssidChar >>= 1;
|
||||
|
||||
int ssid = 0b1111 & ssidChar;
|
||||
|
||||
if (ssid) {
|
||||
address += '-';
|
||||
address += ssid;
|
||||
}
|
||||
if (isRelay && hasBeenDigipited) {
|
||||
address += '*';
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
String encapsulateKISS(const String& ax25Frame, uint8_t cmd) {
|
||||
String kissFrame = "";
|
||||
kissFrame += (char)FEND;
|
||||
kissFrame += (char)(0x0f & cmd);
|
||||
|
||||
for (int i = 0; i < ax25Frame.length(); ++i) {
|
||||
char currentChar = ax25Frame.charAt(i);
|
||||
if (currentChar == (char)FEND) {
|
||||
kissFrame += (char)FESC;
|
||||
kissFrame += (char)TFEND;
|
||||
} else if (currentChar == (char)FESC) {
|
||||
kissFrame += (char)FESC;
|
||||
kissFrame += (char)TFESC;
|
||||
} else {
|
||||
kissFrame += currentChar;
|
||||
}
|
||||
}
|
||||
kissFrame += (char)FEND; // end of frame
|
||||
return kissFrame;
|
||||
}
|
||||
|
||||
|
||||
String decapsulateKISS(const String& frame) {
|
||||
String ax25Frame = "";
|
||||
for (int i = 2; i < frame.length() - 1; ++i) {
|
||||
char currentChar = frame.charAt(i);
|
||||
if (currentChar == (char)FESC) {
|
||||
char nextChar = frame.charAt(i + 1);
|
||||
if (nextChar == (char)TFEND) {
|
||||
ax25Frame += (char)FEND;
|
||||
} else if (nextChar == (char)TFESC) {
|
||||
ax25Frame += (char)FESC;
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
ax25Frame += currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
return ax25Frame;
|
||||
}
|
||||
|
||||
String encodeKISS(const String& frame) {
|
||||
String ax25Frame = "";
|
||||
|
||||
if (validateTNC2Frame(frame)) {
|
||||
String address = "";
|
||||
bool dstAddresWritten = false;
|
||||
for (int p = 0; p <= frame.indexOf(':'); p++) {
|
||||
char currentChar = frame.charAt(p);
|
||||
if (currentChar == ':' || currentChar == '>' || currentChar == ',') {
|
||||
if (!dstAddresWritten && (currentChar == ',' || currentChar == ':')) {
|
||||
ax25Frame = encodeAddressAX25(address) + ax25Frame;
|
||||
dstAddresWritten = true;
|
||||
} else {
|
||||
ax25Frame += encodeAddressAX25(address);
|
||||
}
|
||||
address = "";
|
||||
} else {
|
||||
address += currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
auto lastAddressChar = (uint8_t)ax25Frame.charAt(ax25Frame.length() - 1);
|
||||
ax25Frame.setCharAt(ax25Frame.length() - 1, (char)(lastAddressChar | IS_LAST_ADDRESS_POSITION_MASK));
|
||||
ax25Frame += (char)APRS_CONTROL_FIELD;
|
||||
ax25Frame += (char)APRS_INFORMATION_FIELD;
|
||||
ax25Frame += frame.substring(frame.indexOf(':') + 1);
|
||||
}
|
||||
|
||||
String kissFrame = encapsulateKISS(ax25Frame, CMD_DATA);
|
||||
return kissFrame;
|
||||
}
|
||||
|
||||
String decodeKISS(const String& inputFrame, bool& dataFrame) {
|
||||
String frame = "";
|
||||
|
||||
if (validateKISSFrame(inputFrame)) {
|
||||
dataFrame = inputFrame.charAt(1) == CMD_DATA;
|
||||
if (dataFrame) {
|
||||
String ax25Frame = decapsulateKISS(inputFrame);
|
||||
bool isLast = false;
|
||||
String dstAddr = decodeAddressAX25(ax25Frame.substring(0, 7), isLast, false);
|
||||
String srcAddr = decodeAddressAX25(ax25Frame.substring(7, 14), isLast, false);
|
||||
|
||||
frame = srcAddr + ">" + dstAddr;
|
||||
|
||||
int digiInfoIndex = 14;
|
||||
while (!isLast && digiInfoIndex + 7 < ax25Frame.length()) {
|
||||
String digiAddr = decodeAddressAX25(ax25Frame.substring(digiInfoIndex, digiInfoIndex + 7), isLast, true);
|
||||
frame += ',' + digiAddr;
|
||||
digiInfoIndex += 7;
|
||||
}
|
||||
|
||||
frame += ':';
|
||||
frame += ax25Frame.substring(digiInfoIndex + 2);
|
||||
} else {
|
||||
frame += inputFrame;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
15
src/kiss_utils.h
Normal file
15
src/kiss_utils.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef KISS_UTILS_H_
|
||||
#define KISS_UTILS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
String encodeAddressAX25(String tnc2Address);
|
||||
String decodeAddressAX25(const String& ax25Address, bool& isLast, bool isRelay);
|
||||
|
||||
String encapsulateKISS(const String& ax25Frame, uint8_t cmd);
|
||||
String decapsulateKISS(const String& frame);
|
||||
|
||||
String encodeKISS(const String& frame);
|
||||
String decodeKISS(const String& inputFrame, bool& dataFrame);
|
||||
|
||||
#endif
|
||||
113
src/tnc_utils.cpp
Normal file
113
src/tnc_utils.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#include <WiFi.h>
|
||||
#include "kiss_utils.h"
|
||||
#include "kiss_protocol.h"
|
||||
#include "lora_utils.h"
|
||||
|
||||
#define MAX_CLIENTS 4
|
||||
#define INPUT_TNC_BUFFER_SIZE (2 + MAX_CLIENTS)
|
||||
|
||||
#define TNC_PORT 8001
|
||||
|
||||
WiFiClient* clients[MAX_CLIENTS];
|
||||
|
||||
WiFiServer tncServer(TNC_PORT);
|
||||
|
||||
String inputBuffer[INPUT_TNC_BUFFER_SIZE];
|
||||
|
||||
namespace TNC_Utils {
|
||||
void setup() {
|
||||
tncServer.stop();
|
||||
tncServer.begin();
|
||||
}
|
||||
|
||||
void checkNewClients() {
|
||||
WiFiClient new_client = tncServer.available();
|
||||
|
||||
if (new_client.connected()) {
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
WiFiClient* client = clients[i];
|
||||
|
||||
if (client == nullptr) {
|
||||
clients[i] = new WiFiClient(new_client);
|
||||
|
||||
Serial.println("New TNC client connected");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleInputData(char character, int bufferIndex) {
|
||||
String* inTNCData = &inputBuffer[bufferIndex];
|
||||
|
||||
if (inTNCData->length() == 0 && character != (char)FEND) {
|
||||
return;
|
||||
}
|
||||
|
||||
inTNCData->concat(character);
|
||||
|
||||
if (character == (char)FEND && inTNCData->length() > 3) {
|
||||
bool isDataFrame = false;
|
||||
const String& frame = decodeKISS(*inTNCData, isDataFrame);
|
||||
|
||||
if (isDataFrame) {
|
||||
Serial.print("---> Got from TNC : ");
|
||||
Serial.println(frame);
|
||||
|
||||
LoRa_Utils::sendNewPacket("APRS", frame);
|
||||
}
|
||||
|
||||
inTNCData->clear();
|
||||
}
|
||||
|
||||
if (inTNCData->length() > 255) {
|
||||
inTNCData->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void readFromClients() {
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
auto client = clients[i];
|
||||
if (client != nullptr) {
|
||||
if (client->connected()) {
|
||||
while (client->available() > 0) {
|
||||
char character = client->read();
|
||||
handleInputData(character, 2 + i);
|
||||
}
|
||||
} else {
|
||||
delete client;
|
||||
clients[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendToClients(String packet) {
|
||||
packet = packet.substring(3);
|
||||
|
||||
const String kissEncoded = encodeKISS(packet);
|
||||
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
auto client = clients[i];
|
||||
if (client != nullptr) {
|
||||
if (client->connected()) {
|
||||
client->print(kissEncoded);
|
||||
client->flush();
|
||||
} else {
|
||||
delete client;
|
||||
clients[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.print("---> Sent to TNC : ");
|
||||
Serial.println(packet);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
checkNewClients();
|
||||
|
||||
readFromClients();
|
||||
}
|
||||
}
|
||||
15
src/tnc_utils.h
Normal file
15
src/tnc_utils.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef TNC_UTILS_H_
|
||||
#define TNC_UTILS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace TNC_Utils {
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
void sendToClients(String packet);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Reference in a new issue