kopia lustrzana https://github.com/sh123/esp32_loraprs
Add kiss tx queue and move config defiens to config
rodzic
baa3c06df9
commit
30601b75e8
|
@ -43,6 +43,7 @@ Install via libraries:
|
|||
- Arduino ESP32 library: https://github.com/espressif/arduino-esp32
|
||||
- LoRa arduino library: https://github.com/sandeepmistry/arduino-LoRa
|
||||
- Arduino Timer library: https://github.com/contrem/arduino-timer
|
||||
- cppQueue library: https://github.com/SMFSW/Queue
|
||||
|
||||
# Software Setup
|
||||
- when setting up APRSDroid, use **"TNC (KISS)"** connection protocol in Connection Preferences -> Connection Protocol
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#define CFG_IS_CLIENT_MODE true
|
||||
|
||||
#define CFG_LORA_FREQ 433.775E6
|
||||
#define CFG_LORA_BW 125e3
|
||||
#define CFG_LORA_SF 12
|
||||
#define CFG_LORA_CR 7
|
||||
#define CFG_LORA_PWR 20
|
||||
#define CFG_LORA_ENABLE_CRC true
|
||||
|
||||
#define CFG_BT_NAME "loraprs"
|
||||
|
||||
#define CFG_APRS_LOGIN "NOCALL-10"
|
||||
#define CFG_APRS_PASS "12345"
|
||||
#define CFG_APRS_FILTER "r/35.60/139.80/25"
|
||||
#define CFG_APRS_RAW_BKN "NOCALL-10>APZMDM,WIDE1-1:!0000.00N/00000.00E#LoRA 433.775MHz/BW125/SF12/CR7/0x34"
|
||||
|
||||
#define CFG_WIFI_SSID "<ssid>"
|
||||
#define CFG_WIFI_KEY "<key>"
|
||||
|
||||
#define CFG_FREQ_CORR false
|
||||
#define CFG_PERSISTENT_APRS true
|
||||
#define CFG_DIGIREPEAT false
|
||||
#define CFG_RF_TO_IS true
|
||||
#define CFG_IS_TO_RF false
|
||||
#define CFG_BEACON false
|
|
@ -3,62 +3,77 @@
|
|||
#include "loraprs_service.h"
|
||||
|
||||
#define LED_BUILTIN 2
|
||||
#define LED_TOGGLE_PERIOD 1000
|
||||
#define LED_TOGGLE_PERIOD 1000
|
||||
|
||||
LoraPrs::Config cfg;
|
||||
LoraPrs::Service loraPrsService;
|
||||
#if __has_include("/tmp/esp32_loraprs_config.h")
|
||||
#pragma message("Using external config")
|
||||
#include "/tmp/esp32_loraprs_config.h"
|
||||
#else
|
||||
#pragma message("Using default config")
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
auto watchdogLedTimer = timer_create_default();
|
||||
#if CFG_IS_CLIENT_MODE == true
|
||||
#pragma message("Configured for client mode")
|
||||
#else
|
||||
#pragma message("Configured for server mode")
|
||||
#endif
|
||||
|
||||
void initializeConfig() {
|
||||
void initializeConfig(LoraPrs::Config &cfg) {
|
||||
|
||||
// client/server mode switch
|
||||
cfg.IsClientMode = true;
|
||||
cfg.IsClientMode = CFG_IS_CLIENT_MODE;
|
||||
|
||||
// lora parameters
|
||||
cfg.LoraFreq = 433.775E6;
|
||||
cfg.LoraBw = 125e3;
|
||||
cfg.LoraSf = 12;
|
||||
cfg.LoraCodingRate = 7;
|
||||
cfg.LoraFreq = CFG_LORA_FREQ;
|
||||
cfg.LoraBw = CFG_LORA_BW;
|
||||
cfg.LoraSf = CFG_LORA_SF;
|
||||
cfg.LoraCodingRate = CFG_LORA_CR;
|
||||
cfg.LoraSync = 0x34;
|
||||
cfg.LoraPower = 20;
|
||||
cfg.LoraEnableCrc = true; // set to false for speech data
|
||||
cfg.LoraPower = CFG_LORA_PWR;
|
||||
cfg.LoraEnableCrc = CFG_LORA_ENABLE_CRC; // set to false for speech data
|
||||
|
||||
// aprs configuration
|
||||
cfg.AprsHost = "rotate.aprs2.net";
|
||||
cfg.AprsPort = 14580;
|
||||
cfg.AprsLogin = "NOCALL-10";
|
||||
cfg.AprsPass = "12345";
|
||||
cfg.AprsFilter = "r/35.60/139.80/25"; // multiple filters are space separated
|
||||
cfg.AprsRawBeacon = "NOCALL-10>APZMDM,WIDE1-1:!0000.00N/00000.00E#LoRA 433.775MHz/BW125/SF12/CR7/0x34";
|
||||
cfg.AprsLogin = CFG_APRS_LOGIN;
|
||||
cfg.AprsPass = CFG_APRS_PASS;
|
||||
cfg.AprsFilter = CFG_APRS_FILTER; // multiple filters are space separated
|
||||
cfg.AprsRawBeacon = CFG_APRS_RAW_BKN;
|
||||
cfg.AprsRawBeaconPeriodMinutes = 20;
|
||||
|
||||
// bluetooth device name
|
||||
cfg.BtName = "loraprs";
|
||||
cfg.BtName = CFG_BT_NAME;
|
||||
|
||||
// server mode wifi paramaters
|
||||
cfg.WifiSsid = "<wifi ssid>";
|
||||
cfg.WifiKey = "<wifi key>";
|
||||
cfg.WifiSsid = CFG_WIFI_SSID;
|
||||
cfg.WifiKey = CFG_WIFI_KEY;
|
||||
|
||||
// configuration flags and features
|
||||
cfg.EnableAutoFreqCorrection = false; // automatic tune to any incoming packet frequency
|
||||
cfg.EnableAutoFreqCorrection = CFG_FREQ_CORR; // automatic tune to any incoming packet frequency
|
||||
cfg.EnableSignalReport = true; // signal report will be added to the comment sent to aprsis
|
||||
cfg.EnablePersistentAprsConnection = true; // keep aprsis connection open, otherwise connect on new data only
|
||||
cfg.EnableRfToIs = true; // send data from rf to aprsis
|
||||
cfg.EnableIsToRf = false; // send data from aprsis to rf
|
||||
cfg.EnableRepeater = false; // digirepeat incoming packets
|
||||
cfg.EnableBeacon = false; // enable periodic AprsRawBeacon beacon to rf and aprsis if rf to aprsis is enabled
|
||||
cfg.EnablePersistentAprsConnection = CFG_PERSISTENT_APRS; // keep aprsis connection open, otherwise connect on new data only
|
||||
cfg.EnableRfToIs = CFG_RF_TO_IS; // send data from rf to aprsis
|
||||
cfg.EnableIsToRf = CFG_IS_TO_RF; // send data from aprsis to rf
|
||||
cfg.EnableRepeater = CFG_DIGIREPEAT; // digirepeat incoming packets
|
||||
cfg.EnableBeacon = CFG_BEACON; // enable periodic AprsRawBeacon beacon to rf and aprsis if rf to aprsis is enabled
|
||||
}
|
||||
|
||||
LoraPrs::Service loraPrsService;
|
||||
|
||||
auto watchdogLedTimer = timer_create_default();
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, 1);
|
||||
|
||||
Serial.begin(115200);
|
||||
while (!Serial);
|
||||
|
||||
LoraPrs::Config config;
|
||||
|
||||
initializeConfig();
|
||||
loraPrsService.setup(cfg);
|
||||
initializeConfig(config);
|
||||
loraPrsService.setup(config);
|
||||
|
||||
watchdogLedTimer.every(LED_TOGGLE_PERIOD, toggleWatchdogLed);
|
||||
}
|
||||
|
|
|
@ -7,10 +7,15 @@ Service::Service()
|
|||
, kissCmd_(KissCmd::NoCmd)
|
||||
, csmaP_(CfgCsmaPersistence)
|
||||
, csmaSlotTime_(CfgCsmaSlotTimeMs)
|
||||
, txQueue_(new cppQueue(sizeof(unsigned char), CfgLoraTxQueueSize))
|
||||
, serialBt_()
|
||||
{
|
||||
}
|
||||
|
||||
Service::~Service() {
|
||||
delete txQueue_;
|
||||
}
|
||||
|
||||
void Service::setup(const Config &conf)
|
||||
{
|
||||
previousBeaconMs_ = 0;
|
||||
|
@ -141,13 +146,14 @@ void Service::setupBt(const String &btName)
|
|||
}
|
||||
|
||||
void Service::loop()
|
||||
{
|
||||
{
|
||||
if (needsWifi() && WiFi.status() != WL_CONNECTED) {
|
||||
reconnectWifi();
|
||||
}
|
||||
if (needsAprsis() && !aprsisConn_.connected() && persistentConn_) {
|
||||
reconnectAprsis();
|
||||
}
|
||||
|
||||
// RX path
|
||||
if (int packetSize = LoRa.parsePacket()) {
|
||||
onLoraDataAvailable(packetSize);
|
||||
|
@ -155,14 +161,14 @@ void Service::loop()
|
|||
// TX path
|
||||
else {
|
||||
if (random(0, 255) < csmaP_) {
|
||||
if (serialBt_.available()) {
|
||||
onBtDataAvailable();
|
||||
}
|
||||
else if (aprsisConn_.available() > 0) {
|
||||
if (aprsisConn_.available() > 0) {
|
||||
onAprsisDataAvailable();
|
||||
}
|
||||
else if (needsBeacon()) {
|
||||
sendPeriodicBeacon();
|
||||
}
|
||||
else {
|
||||
processTx();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -179,7 +185,7 @@ void Service::sendPeriodicBeacon()
|
|||
if (previousBeaconMs_ == 0 || currentMs - previousBeaconMs_ >= aprsBeaconPeriodMinutes_ * 60 * 1000) {
|
||||
AX25::Payload payload(aprsBeacon_);
|
||||
if (payload.IsValid()) {
|
||||
sendToLora(payload);
|
||||
sendAX25ToLora(payload);
|
||||
if (enableRfToIs_) {
|
||||
sendToAprsis(payload.ToString());
|
||||
}
|
||||
|
@ -222,7 +228,7 @@ void Service::onAprsisDataAvailable()
|
|||
if (enableIsToRf_ && aprsisData.length() > 0) {
|
||||
AX25::Payload payload(aprsisData);
|
||||
if (payload.IsValid()) {
|
||||
sendToLora(payload);
|
||||
sendAX25ToLora(payload);
|
||||
}
|
||||
else {
|
||||
Serial.println("Invalid payload from APRSIS");
|
||||
|
@ -230,7 +236,7 @@ void Service::onAprsisDataAvailable()
|
|||
}
|
||||
}
|
||||
|
||||
bool Service::sendToLora(const AX25::Payload &payload)
|
||||
bool Service::sendAX25ToLora(const AX25::Payload &payload)
|
||||
{
|
||||
byte buf[512];
|
||||
int bytesWritten = payload.ToBinary(buf, sizeof(buf));
|
||||
|
@ -305,7 +311,7 @@ void Service::onLoraDataAvailable(int packetSize)
|
|||
Serial.println("Packet sent to APRS-IS");
|
||||
}
|
||||
if (enableRepeater_ && payload.Digirepeat(ownCallsign_)) {
|
||||
sendToLora(payload);
|
||||
sendAX25ToLora(payload);
|
||||
Serial.println("Packet digirepeated");
|
||||
}
|
||||
}
|
||||
|
@ -315,102 +321,111 @@ void Service::onLoraDataAvailable(int packetSize)
|
|||
}
|
||||
}
|
||||
|
||||
void Service::processTx()
|
||||
{
|
||||
while (serialBt_.available() || !txQueue_->isEmpty()) {
|
||||
|
||||
if (serialBt_.available()) {
|
||||
int rxResult = serialBt_.read();
|
||||
if (rxResult != -1) {
|
||||
byte rxByte = (byte)rxResult;
|
||||
if (!txQueue_->push((void *)&rxByte)) {
|
||||
Serial.println("TX queue is full");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!txQueue_->isEmpty()) {
|
||||
byte qRxByte;
|
||||
if (txQueue_->peek((void *)&qRxByte)) {
|
||||
if (kissReceiveByte(qRxByte)) {
|
||||
txQueue_->drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Service::kissResetState()
|
||||
{
|
||||
kissCmd_ = KissCmd::NoCmd;
|
||||
kissState_ = KissState::Void;
|
||||
}
|
||||
|
||||
bool Service::loraBeginPacketAndWait()
|
||||
{
|
||||
for (int i = 0; i < CfgLoraTxWaitMs; i++) {
|
||||
if (LoRa.beginPacket() == 1) {
|
||||
bool Service::kissProcessCommand(unsigned char rxByte) {
|
||||
|
||||
switch (rxByte) {
|
||||
case KissCmd::Data:
|
||||
if (LoRa.beginPacket() == 0) return false;
|
||||
kissState_ = KissState::GetData;
|
||||
break;
|
||||
case KissCmd::P:
|
||||
kissState_ = KissState::GetP;
|
||||
break;
|
||||
case KissCmd::SlotTime:
|
||||
kissState_ = KissState::GetSlotTime;
|
||||
break;
|
||||
default:
|
||||
// unknown command
|
||||
kissResetState();
|
||||
return true;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
return false;
|
||||
kissCmd_ = (KissCmd)rxByte;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Service::onBtDataAvailable()
|
||||
{
|
||||
while (serialBt_.available()) {
|
||||
|
||||
int rxResult = serialBt_.read();
|
||||
if (rxResult == -1) break;
|
||||
|
||||
byte rxByte = (byte)rxResult;
|
||||
bool Service::kissReceiveByte(unsigned char rxByte) {
|
||||
|
||||
switch (kissState_) {
|
||||
case KissState::Void:
|
||||
if (rxByte == KissMarker::Fend) {
|
||||
kissCmd_ = KissCmd::NoCmd;
|
||||
kissState_ = KissState::GetCmd;
|
||||
switch (kissState_) {
|
||||
case KissState::Void:
|
||||
if (rxByte == KissMarker::Fend) {
|
||||
kissCmd_ = KissCmd::NoCmd;
|
||||
kissState_ = KissState::GetCmd;
|
||||
}
|
||||
break;
|
||||
case KissState::GetCmd:
|
||||
if (rxByte != KissMarker::Fend) {
|
||||
if (!kissProcessCommand(rxByte)) return false;
|
||||
}
|
||||
break;
|
||||
case KissState::GetP:
|
||||
csmaP_ = rxByte;
|
||||
kissState_ = KissState::GetData;
|
||||
break;
|
||||
case KissState::GetSlotTime:
|
||||
csmaSlotTime_ = (long)rxByte * 10;
|
||||
kissState_ = KissState::GetData;
|
||||
break;
|
||||
case KissState::GetData:
|
||||
if (rxByte == KissMarker::Fesc) {
|
||||
kissState_ = KissState::Escape;
|
||||
}
|
||||
else if (rxByte == KissMarker::Fend) {
|
||||
if (kissCmd_ == KissCmd::Data) {
|
||||
LoRa.endPacket(true);
|
||||
}
|
||||
break;
|
||||
case KissState::GetCmd:
|
||||
if (rxByte != KissMarker::Fend) {
|
||||
if (rxByte == KissCmd::Data) {
|
||||
if (!loraBeginPacketAndWait()) {
|
||||
Serial.println("LoRa buffer overflow, dropping packet");
|
||||
kissResetState();
|
||||
return;
|
||||
}
|
||||
kissCmd_ = (KissCmd)rxByte;
|
||||
kissState_ = KissState::GetData;
|
||||
}
|
||||
else if (rxByte == KissCmd::P) {
|
||||
kissCmd_ = (KissCmd)rxByte;
|
||||
kissState_ = KissState::GetP;
|
||||
}
|
||||
else if (rxByte == KissCmd::SlotTime) {
|
||||
kissCmd_ = (KissCmd)rxByte;
|
||||
kissState_ = KissState::GetSlotTime;
|
||||
}
|
||||
else {
|
||||
kissResetState();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KissState::GetP:
|
||||
csmaP_ = rxByte;
|
||||
kissResetState();
|
||||
}
|
||||
else if (kissCmd_ == KissCmd::Data) {
|
||||
LoRa.write(rxByte);
|
||||
}
|
||||
break;
|
||||
case KissState::Escape:
|
||||
if (rxByte == KissMarker::Tfend) {
|
||||
LoRa.write(KissMarker::Fend);
|
||||
kissState_ = KissState::GetData;
|
||||
break;
|
||||
case KissState::GetSlotTime:
|
||||
csmaSlotTime_ = (long)rxByte * 10;
|
||||
}
|
||||
else if (rxByte == KissMarker::Tfesc) {
|
||||
LoRa.write(KissMarker::Fesc);
|
||||
kissState_ = KissState::GetData;
|
||||
break;
|
||||
case KissState::GetData:
|
||||
if (rxByte == KissMarker::Fesc) {
|
||||
kissState_ = KissState::Escape;
|
||||
}
|
||||
else if (rxByte == KissMarker::Fend) {
|
||||
if (kissCmd_ == KissCmd::Data) {
|
||||
LoRa.endPacket(true);
|
||||
}
|
||||
kissResetState();
|
||||
}
|
||||
else if (kissCmd_ == KissCmd::Data) {
|
||||
LoRa.write(rxByte);
|
||||
}
|
||||
break;
|
||||
case KissState::Escape:
|
||||
if (rxByte == KissMarker::Tfend) {
|
||||
LoRa.write(KissMarker::Fend);
|
||||
kissState_ = KissState::GetData;
|
||||
}
|
||||
else if (rxByte == KissMarker::Tfesc) {
|
||||
LoRa.write(KissMarker::Fesc);
|
||||
kissState_ = KissState::GetData;
|
||||
}
|
||||
else {
|
||||
kissResetState();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
kissResetState();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // LoraPrs
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <SPI.h>
|
||||
#include <LoRa.h>
|
||||
#include <WiFi.h>
|
||||
#include <cppQueue.h>
|
||||
|
||||
#include "BluetoothSerial.h"
|
||||
#include "ax25_payload.h"
|
||||
|
@ -16,6 +17,7 @@ class Service
|
|||
{
|
||||
public:
|
||||
Service();
|
||||
~Service();
|
||||
|
||||
void setup(const Config &conf);
|
||||
void loop();
|
||||
|
@ -28,16 +30,17 @@ private:
|
|||
void reconnectWifi();
|
||||
bool reconnectAprsis();
|
||||
|
||||
void processTx();
|
||||
|
||||
void onLoraDataAvailable(int packetSize);
|
||||
void onBtDataAvailable();
|
||||
void onAprsisDataAvailable();
|
||||
|
||||
void sendPeriodicBeacon();
|
||||
|
||||
void sendToAprsis(String aprsMessage);
|
||||
bool sendToLora(const AX25::Payload &payload);
|
||||
|
||||
bool loraBeginPacketAndWait();
|
||||
bool sendAX25ToLora(const AX25::Payload &payload);
|
||||
|
||||
bool kissReceiveByte(unsigned char rxByte);
|
||||
bool kissProcessCommand(unsigned char rxByte);
|
||||
void kissResetState();
|
||||
|
||||
inline bool needsAprsis() const { return !isClient_ && (enableRfToIs_ || enableIsToRf_); }
|
||||
|
@ -72,7 +75,7 @@ private:
|
|||
const String CfgLoraprsVersion = "LoRAPRS 0.1";
|
||||
|
||||
const int CfgPollDelayMs = 5;
|
||||
const int CfgLoraTxWaitMs = 2000;
|
||||
const int CfgLoraTxQueueSize = 1024;
|
||||
|
||||
// tx when lower than this value from random 0..255
|
||||
// use lower value for high traffic, use 255 for real time
|
||||
|
@ -110,6 +113,7 @@ private:
|
|||
long previousBeaconMs_;
|
||||
byte csmaP_;
|
||||
long csmaSlotTime_;
|
||||
cppQueue *txQueue_;
|
||||
|
||||
// peripherals
|
||||
BluetoothSerial serialBt_;
|
||||
|
|
Ładowanie…
Reference in New Issue