esp32_loraprs/loraprs_service.cpp

440 wiersze
10 KiB
C++
Czysty Zwykły widok Historia

2020-06-14 18:55:27 +00:00
#include "loraprs_service.h"
2020-06-14 18:55:27 +00:00
namespace LoraPrs {
Service::Service()
: kissState_(KissState::Void)
2019-05-09 16:13:08 +00:00
, kissCmd_(KissCmd::NoCmd)
, csmaP_(CfgCsmaPersistence)
, csmaSlotTime_(CfgCsmaSlotTimeMs)
2021-02-01 10:26:25 +00:00
, kissTxQueue_(new cppQueue(sizeof(unsigned char), CfgLoraTxQueueSize))
, serialBt_()
{
}
2020-06-14 18:55:27 +00:00
void Service::setup(const Config &conf)
{
previousBeaconMs_ = 0;
// config
isClient_ = conf.IsClientMode;
loraFreq_ = conf.LoraFreq;
ownCallsign_ = AX25::Callsign(conf.AprsLogin);
if (!ownCallsign_.IsValid()) {
Serial.println("Own callsign is not valid");
}
aprsLogin_ = String("user ") + conf.AprsLogin + String(" pass ") +
conf.AprsPass + String(" vers ") + CfgLoraprsVersion;
if (conf.AprsFilter.length() > 0) {
aprsLogin_ += String(" filter ") + conf.AprsFilter;
}
aprsLogin_ += String("\n");
aprsHost_ = conf.AprsHost;
aprsPort_ = conf.AprsPort;
aprsBeacon_ = conf.AprsRawBeacon;
aprsBeaconPeriodMinutes_ = conf.AprsRawBeaconPeriodMinutes;
autoCorrectFreq_ = conf.EnableAutoFreqCorrection;
addSignalReport_ = conf.EnableSignalReport;
persistentConn_ = conf.EnablePersistentAprsConnection;
enableRfToIs_ = conf.EnableRfToIs;
enableIsToRf_ = conf.EnableIsToRf;
enableRepeater_ = conf.EnableRepeater;
enableBeacon_ = conf.EnableBeacon;
// peripherals
setupLora(conf.LoraFreq, conf.LoraBw, conf.LoraSf,
conf.LoraCodingRate, conf.LoraPower, conf.LoraSync, conf.LoraEnableCrc);
if (needsWifi()) {
setupWifi(conf.WifiSsid, conf.WifiKey);
}
if (needsBt() || conf.BtName.length() > 0) {
setupBt(conf.BtName);
}
if (needsAprsis() && persistentConn_) {
reconnectAprsis();
}
}
2020-06-14 18:55:27 +00:00
void Service::setupWifi(const String &wifiName, const String &wifiKey)
{
if (!isClient_) {
Serial.print("WIFI connecting to " + wifiName);
2019-05-02 16:09:04 +00:00
WiFi.setHostname("loraprs");
WiFi.mode(WIFI_STA);
WiFi.begin(wifiName.c_str(), wifiKey.c_str());
int retryCnt = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(CfgConnRetryMs);
Serial.print(".");
if (retryCnt++ >= CfgWiFiConnRetryMaxTimes) {
Serial.println("failed");
return;
}
}
Serial.println("ok");
Serial.println(WiFi.localIP());
}
}
2020-06-14 18:55:27 +00:00
void Service::reconnectWifi()
{
2019-05-02 16:09:04 +00:00
Serial.print("WIFI re-connecting...");
int retryCnt = 0;
2019-05-02 16:09:04 +00:00
while (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0,0,0,0)) {
WiFi.reconnect();
delay(CfgConnRetryMs);
2019-05-02 16:09:04 +00:00
Serial.print(".");
if (retryCnt++ >= CfgWiFiConnRetryMaxTimes) {
Serial.println("failed");
return;
}
2019-05-02 16:09:04 +00:00
}
Serial.println("ok");
}
2020-06-14 18:55:27 +00:00
bool Service::reconnectAprsis()
{
Serial.print("APRSIS connecting...");
if (!aprsisConn_.connect(aprsHost_.c_str(), aprsPort_)) {
Serial.println("Failed to connect to " + aprsHost_ + ":" + aprsPort_);
return false;
}
Serial.println("ok");
aprsisConn_.print(aprsLogin_);
return true;
}
void Service::setupLora(long loraFreq, long bw, int sf, int cr, int pwr, int sync, bool enableCrc)
{
Serial.print("LoRa init...");
LoRa.setPins(CfgPinSs, CfgPinRst, CfgPinDio0);
while (!LoRa.begin(loraFreq)) {
Serial.print(".");
delay(CfgConnRetryMs);
}
LoRa.setSyncWord(sync);
LoRa.setSpreadingFactor(sf);
LoRa.setSignalBandwidth(bw);
LoRa.setCodingRate4(cr);
LoRa.setTxPower(pwr);
if (enableCrc) {
LoRa.enableCrc();
}
Serial.println("ok");
}
2020-06-14 18:55:27 +00:00
void Service::setupBt(const String &btName)
{
Serial.print("BT init " + btName + "...");
if (serialBt_.begin(btName)) {
Serial.println("ok");
}
else
{
Serial.println("failed");
}
}
2020-06-14 18:55:27 +00:00
void Service::loop()
{
if (needsWifi() && WiFi.status() != WL_CONNECTED) {
reconnectWifi();
}
if (needsAprsis() && !aprsisConn_.connected() && persistentConn_) {
reconnectAprsis();
}
2020-06-20 10:04:17 +00:00
// RX path
2019-05-09 16:13:08 +00:00
if (int packetSize = LoRa.parsePacket()) {
onLoraDataAvailable(packetSize);
}
2020-06-20 10:04:17 +00:00
// TX path
2020-12-08 13:26:14 +00:00
else {
if (random(0, 255) < csmaP_) {
if (aprsisConn_.available() > 0) {
2020-12-08 13:26:14 +00:00
onAprsisDataAvailable();
}
else if (needsBeacon()) {
sendPeriodicBeacon();
}
else {
processTx();
2020-12-08 13:26:14 +00:00
}
2020-06-20 09:50:57 +00:00
}
2020-12-08 13:26:14 +00:00
else {
delay(csmaSlotTime_);
2020-06-20 09:50:57 +00:00
}
}
2020-06-19 07:39:49 +00:00
delay(CfgPollDelayMs);
}
2020-06-19 07:39:49 +00:00
void Service::sendPeriodicBeacon()
{
long currentMs = millis();
if (previousBeaconMs_ == 0 || currentMs - previousBeaconMs_ >= aprsBeaconPeriodMinutes_ * 60 * 1000) {
AX25::Payload payload(aprsBeacon_);
if (payload.IsValid()) {
sendAX25ToLora(payload);
if (enableRfToIs_) {
2020-06-17 10:32:00 +00:00
sendToAprsis(payload.ToString());
}
2020-06-17 10:32:00 +00:00
Serial.println("Periodic beacon is sent");
}
else {
Serial.println("Beacon payload is invalid");
}
previousBeaconMs_ = currentMs;
}
}
2020-06-17 10:32:00 +00:00
2020-06-14 18:55:27 +00:00
void Service::sendToAprsis(String aprsMessage)
{
if (needsWifi() && WiFi.status() != WL_CONNECTED) {
reconnectWifi();
2019-05-09 16:13:08 +00:00
}
if (needsAprsis() && !aprsisConn_.connected()) {
reconnectAprsis();
2019-05-09 16:13:08 +00:00
}
2020-06-17 10:32:00 +00:00
aprsisConn_.println(aprsMessage);
2019-05-09 16:13:08 +00:00
if (!persistentConn_) {
aprsisConn_.stop();
2019-05-09 16:13:08 +00:00
}
}
2020-06-14 18:55:27 +00:00
void Service::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()) {
sendAX25ToLora(payload);
}
else {
Serial.println("Unknown payload from APRSIS, ignoring...");
}
}
}
bool Service::sendAX25ToLora(const AX25::Payload &payload)
{
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();
return true;
}
2020-06-14 18:55:27 +00:00
void Service::onLoraDataAvailable(int packetSize)
{
2019-05-09 16:13:08 +00:00
int rxBufIndex = 0;
byte rxBuf[packetSize];
serialBt_.write(KissMarker::Fend);
serialBt_.write(KissCmd::Data);
while (LoRa.available()) {
2019-05-09 16:13:08 +00:00
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);
}
}
2019-05-09 16:13:08 +00:00
serialBt_.write(KissMarker::Fend);
float snr = LoRa.packetSnr();
2020-02-12 16:08:58 +00:00
float rssi = LoRa.packetRssi();
long frequencyError = LoRa.packetFrequencyError();
2020-06-05 13:19:36 +00:00
if (autoCorrectFreq_) {
loraFreq_ -= frequencyError;
2020-06-05 13:19:36 +00:00
LoRa.setFrequency(loraFreq_);
}
2020-06-19 09:42:31 +00:00
if (!isClient_) {
String signalReport = String(" ") +
String("rssi: ") +
String(snr < 0 ? rssi + snr : rssi) +
String("dBm, ") +
String("snr: ") +
String(snr) +
String("dB, ") +
String("err: ") +
String(frequencyError) +
String("Hz");
AX25::Payload payload(rxBuf, rxBufIndex);
2019-05-09 16:13:08 +00:00
2020-06-19 09:42:31 +00:00
if (payload.IsValid()) {
String textPayload = payload.ToString(addSignalReport_ ? signalReport : String());
Serial.println(textPayload);
2020-06-19 09:42:31 +00:00
if (enableRfToIs_) {
sendToAprsis(textPayload);
Serial.println("Packet sent to APRS-IS");
}
if (enableRepeater_ && payload.Digirepeat(ownCallsign_)) {
sendAX25ToLora(payload);
2020-06-19 09:42:31 +00:00
Serial.println("Packet digirepeated");
}
}
2020-06-19 09:42:31 +00:00
else {
2021-01-02 11:57:50 +00:00
Serial.println("Skipping non-AX25 payload");
}
}
}
void Service::processTx()
{
2021-02-01 10:26:25 +00:00
while (serialBt_.available() || !kissTxQueue_->isEmpty()) {
if (serialBt_.available()) {
int rxResult = serialBt_.read();
if (rxResult != -1) {
byte rxByte = (byte)rxResult;
2021-02-01 10:26:25 +00:00
if (!kissTxQueue_->push((void *)&rxByte)) {
Serial.println("TX queue is full");
}
}
}
2021-02-01 10:26:25 +00:00
if (!kissTxQueue_->isEmpty()) {
byte qRxByte;
2021-02-01 10:26:25 +00:00
if (kissTxQueue_->peek((void *)&qRxByte)) {
if (kissReceiveByte(qRxByte)) {
2021-02-01 10:26:25 +00:00
kissTxQueue_->drop();
}
}
}
yield();
}
}
2020-06-14 18:55:27 +00:00
void Service::kissResetState()
{
2019-05-09 16:13:08 +00:00
kissCmd_ = KissCmd::NoCmd;
kissState_ = KissState::Void;
}
bool Service::kissProcessCommand(unsigned char rxByte) {
switch (rxByte) {
case KissCmd::Data:
delay(CfgPollDelayMs); // LoRa may drop packet if removed
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;
}
kissCmd_ = (KissCmd)rxByte;
return true;
}
bool Service::kissReceiveByte(unsigned char rxByte) {
2019-05-09 16:13:08 +00:00
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);
2019-05-09 16:13:08 +00:00
}
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;
}
return true;
}
2020-06-14 18:55:27 +00:00
} // LoraPrs