LoRa_APRS_iGate/src/LoRa_APRS_iGate.cpp

560 wiersze
15 KiB
C++
Czysty Zwykły widok Historia

#include <map>
2020-03-18 18:49:59 +00:00
#include <Arduino.h>
2020-12-03 20:21:11 +00:00
#include <ETH.h>
2020-03-18 18:49:59 +00:00
#include <WiFiMulti.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
2020-03-19 12:59:51 +00:00
#include <ArduinoOTA.h>
2020-03-18 18:49:59 +00:00
#include <APRS-IS.h>
2020-11-03 22:35:19 +00:00
#include <SPIFFS.h>
#include <ESP-FTP-Server-Lib.h>
#include <FTPFilesystem.h>
2020-06-04 20:27:44 +00:00
2020-11-07 23:07:43 +00:00
#include "logger.h"
2020-06-04 20:27:44 +00:00
#include "LoRa_APRS.h"
2020-03-18 18:49:59 +00:00
2020-07-24 13:29:14 +00:00
#include "pins.h"
2020-03-18 22:24:23 +00:00
#include "display.h"
2020-07-22 20:21:54 +00:00
#include "configuration.h"
2020-03-18 18:49:59 +00:00
2020-05-11 09:57:42 +00:00
#if defined(ARDUINO_T_Beam) && !defined(ARDUINO_T_Beam_V0_7)
#include "power_management.h"
2020-06-01 09:23:48 +00:00
PowerManagement powerManagement;
2020-05-08 22:10:34 +00:00
#endif
2020-03-18 18:49:59 +00:00
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t * timer = NULL;
volatile uint secondsSinceLastAPRSISBeacon = 0;
volatile uint secondsSinceLastDigiBeacon = 0;
volatile uint secondsSinceStartup = 0;
volatile uint secondsSinceDisplay = 0;
WiFiMulti WiFiMulti;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, 60*60);
2020-11-03 22:35:19 +00:00
FTPServer ftpServer;
Configuration Config;
APRS_IS * aprs_is = 0;
LoRa_APRS lora_aprs;
std::shared_ptr<APRSMessage> BeaconMsg;
2020-12-03 20:21:11 +00:00
volatile bool eth_connected = false;
String create_lat_aprs(double lat);
String create_long_aprs(double lng);
2020-03-19 12:29:21 +00:00
2020-07-22 20:21:54 +00:00
void load_config();
2020-04-03 21:32:10 +00:00
void setup_wifi();
2020-12-03 20:21:11 +00:00
void setup_eth();
2020-04-03 21:32:10 +00:00
void setup_ota();
void setup_lora();
void setup_ntp();
2020-07-22 20:21:54 +00:00
void setup_aprs_is();
void setup_timer();
2020-11-03 22:35:19 +00:00
void setup_ftp();
2020-04-03 21:32:10 +00:00
std::map<uint, std::shared_ptr<APRSMessage>> lastMessages;
2020-05-08 21:09:44 +00:00
2020-05-29 19:13:11 +00:00
// cppcheck-suppress unusedFunction
2020-03-18 18:49:59 +00:00
void setup()
{
Serial.begin(115200);
2020-06-01 09:23:48 +00:00
#if defined(ARDUINO_T_Beam) && !defined(ARDUINO_T_Beam_V0_7)
Wire.begin(SDA, SCL);
if (!powerManagement.begin(Wire))
{
2020-11-07 23:07:43 +00:00
logPrintlnI("AXP192 init done!");
}
else
{
logPrintlnE("AXP192 init failed!");
2020-06-01 09:23:48 +00:00
}
powerManagement.activateLoRa();
powerManagement.activateOLED();
powerManagement.deactivateGPS();
#endif
2020-03-18 18:49:59 +00:00
delay(500);
2020-11-07 23:07:43 +00:00
logPrintlnA("LoRa APRS iGate & Digi by OE5BPA (Peter Buchegger)");
2020-11-15 21:18:02 +00:00
logPrintlnA("Version: 20.46.0-dev");
2020-11-07 23:07:43 +00:00
setup_display();
show_display("OE5BPA", "LoRa APRS iGate & Digi", "by Peter Buchegger", "1.0.0-dev", 3000);
2020-03-18 18:49:59 +00:00
2020-07-22 20:21:54 +00:00
load_config();
2020-04-03 21:32:10 +00:00
setup_lora();
2020-12-03 20:21:11 +00:00
#ifdef ETH_BOARD
setup_eth();
setup_ota();
setup_ntp();
setup_ftp();
#else
2020-10-12 19:43:37 +00:00
if(Config.wifi.active)
{
setup_wifi();
setup_ota();
setup_ntp();
2020-11-03 22:35:19 +00:00
setup_ftp();
2020-10-12 19:43:37 +00:00
}
else
{
// make sure wifi and bt is off if we don't need it:
WiFi.mode(WIFI_OFF);
btStop();
}
2020-12-03 20:21:11 +00:00
#endif
if(Config.aprs_is.active) setup_aprs_is();
setup_timer();
2020-05-08 21:09:44 +00:00
if(Config.display.overwritePin != 0)
{
pinMode(Config.display.overwritePin, INPUT);
pinMode(Config.display.overwritePin, INPUT_PULLUP);
}
2020-03-18 18:49:59 +00:00
delay(500);
2020-11-07 23:07:43 +00:00
logPrintlnI("setup done...");
secondsSinceDisplay = 0;
2020-03-18 18:49:59 +00:00
}
2020-05-29 19:13:11 +00:00
// cppcheck-suppress unusedFunction
2020-03-18 18:49:59 +00:00
void loop()
{
static bool display_is_on = true;
if(Config.display.overwritePin != 0 && !digitalRead(Config.display.overwritePin))
{
secondsSinceDisplay = 0;
display_is_on = true;
setup_display();
} else
if(!Config.display.alwaysOn && secondsSinceDisplay > Config.display.timeout && display_is_on)
{
turn_off_display();
display_is_on = false;
}
static bool beacon_aprs_is = Config.aprs_is.active && Config.aprs_is.beacon;
static bool beacon_digi = Config.digi.active && Config.digi.beacon;
if(Config.aprs_is.active && Config.aprs_is.beacon && secondsSinceLastAPRSISBeacon >= (Config.aprs_is.beaconTimeout*60))
{
portENTER_CRITICAL(&timerMux);
secondsSinceLastAPRSISBeacon -= (Config.aprs_is.beaconTimeout*60);
portEXIT_CRITICAL(&timerMux);
beacon_aprs_is = true;
}
if(Config.digi.active && Config.digi.beacon && secondsSinceLastDigiBeacon >= (Config.digi.beaconTimeout*60))
{
portENTER_CRITICAL(&timerMux);
secondsSinceLastDigiBeacon -= (Config.digi.beaconTimeout*60);
portEXIT_CRITICAL(&timerMux);
beacon_digi = true;
}
2020-11-03 23:15:46 +00:00
if(Config.ftp.active)
2020-11-03 22:35:19 +00:00
{
2020-11-03 23:15:46 +00:00
ftpServer.handle();
static bool configWasOpen = false;
if(configWasOpen && ftpServer.countConnections() == 0)
{
2020-11-07 23:07:43 +00:00
logPrintlnW("Maybe the config has been changed via FTP, lets restart now to get the new config...");
2020-11-03 23:15:46 +00:00
Serial.println();
ESP.restart();
}
if(ftpServer.countConnections() > 0)
{
configWasOpen = true;
}
2020-11-03 22:35:19 +00:00
}
2020-12-03 20:21:11 +00:00
if(Config.wifi.active || eth_connected) ArduinoOTA.handle();
if(Config.wifi.active && WiFiMulti.run() != WL_CONNECTED)
2020-03-18 18:49:59 +00:00
{
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
2020-11-07 23:07:43 +00:00
logPrintlnE("WiFi not connected!");
2020-03-19 21:02:24 +00:00
show_display("ERROR", "WiFi not connected!");
2020-03-18 18:49:59 +00:00
delay(1000);
return;
}
2020-12-03 20:21:11 +00:00
if(eth_connected || (Config.aprs_is.active && !aprs_is->connected()))
2020-03-18 18:49:59 +00:00
{
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
2020-11-07 23:07:43 +00:00
logPrintI("connecting to APRS-IS server: ");
logPrintI(Config.aprs_is.server);
logPrintI(" on port: ");
logPrintlnI(String(Config.aprs_is.port));
show_display("INFO", "Connecting to APRS-IS server");
if(!aprs_is->connect(Config.aprs_is.server, Config.aprs_is.port))
2020-03-18 18:49:59 +00:00
{
2020-11-07 23:07:43 +00:00
logPrintlnE("Connection failed.");
logPrintlnI("Waiting 5 seconds before retrying...");
2020-03-19 21:02:24 +00:00
show_display("ERROR", "Server connection failed!", "waiting 5 sec");
2020-03-18 18:49:59 +00:00
delay(5000);
return;
}
2020-11-07 23:07:43 +00:00
logPrintlnI("Connected to APRS-IS server!");
2020-03-18 18:49:59 +00:00
}
if(Config.aprs_is.active && aprs_is->available() > 0)
2020-03-18 18:49:59 +00:00
{
2020-07-22 20:21:54 +00:00
String str = aprs_is->getMessage();
2020-11-07 23:07:43 +00:00
logPrintD("[" + timeClient.getFormattedTime() + "] ");
logPrintlnD(str);
2020-03-18 18:49:59 +00:00
}
2020-06-04 20:27:44 +00:00
if(lora_aprs.hasMessage())
2020-03-18 18:49:59 +00:00
{
2020-06-04 20:27:44 +00:00
std::shared_ptr<APRSMessage> msg = lora_aprs.getMessage();
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
2020-10-31 23:42:20 +00:00
show_display(Config.callsign, timeClient.getFormattedTime() + " LoRa", "RSSI: " + String(lora_aprs.packetRssi()) + ", SNR: " + String(lora_aprs.packetSnr()), msg->toString());
2020-11-07 23:07:43 +00:00
logPrintD("[" + timeClient.getFormattedTime() + "] ");
logPrintD(" Received packet '");
logPrintD(msg->toString());
logPrintD("' with RSSI ");
logPrintD(String(lora_aprs.packetRssi()));
logPrintD(" and SNR ");
logPrintlnD(String(lora_aprs.packetSnr()));
2020-03-18 22:24:23 +00:00
if(Config.aprs_is.active)
{
aprs_is->sendMessage(msg->encode());
}
if(Config.digi.active)
{
if(msg->getSource().indexOf(Config.callsign) != -1)
{
2020-11-07 23:07:43 +00:00
logPrintD("Message already received as repeater: '");
logPrintD(msg->toString());
logPrintD("' with RSSI ");
logPrintD(String(lora_aprs.packetRssi()));
logPrintD(" and SNR ");
logPrintlnD(String(lora_aprs.packetSnr()));
return;
}
// lets try not to flood the LoRa frequency in limiting the same messages:
std::map<uint, std::shared_ptr<APRSMessage>>::iterator foundMsg = std::find_if(lastMessages.begin(), lastMessages.end(), [&](std::pair<const unsigned int, std::shared_ptr<APRSMessage> > & old_msg)
{
if(msg->getSource() == old_msg.second->getSource() &&
msg->getDestination() == old_msg.second->getDestination() &&
msg->getAPRSBody()->getData() == old_msg.second->getAPRSBody()->getData())
{
return true;
}
return false;
});
if(foundMsg == lastMessages.end())
{
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
2020-10-31 23:42:20 +00:00
show_display(Config.callsign, "RSSI: " + String(lora_aprs.packetRssi()) + ", SNR: " + String(lora_aprs.packetSnr()), msg->toString(), 0);
2020-11-07 23:07:43 +00:00
logPrintD("Received packet '");
logPrintD(msg->toString());
logPrintD("' with RSSI ");
logPrintD(String(lora_aprs.packetRssi()));
logPrintD(" and SNR ");
logPrintlnD(String(lora_aprs.packetSnr()));
msg->setPath(String(Config.callsign) + "*");
lora_aprs.sendMessage(msg);
lastMessages.insert({secondsSinceStartup, msg});
}
else
{
2020-11-07 23:07:43 +00:00
logPrintD("Message already received (timeout): '");
logPrintD(msg->toString());
logPrintD("' with RSSI ");
logPrintD(String(lora_aprs.packetRssi()));
logPrintD(" and SNR ");
logPrintlnD(String(lora_aprs.packetSnr()));
}
return;
}
}
if(Config.digi.active)
{
for(std::map<uint, std::shared_ptr<APRSMessage>>::iterator iter = lastMessages.begin(); iter != lastMessages.end(); )
{
if(secondsSinceStartup >= iter->first + Config.digi.forwardTimeout*60)
{
iter = lastMessages.erase(iter);
}
else
{
2020-10-12 19:45:57 +00:00
++iter;
}
}
2020-07-22 20:21:54 +00:00
}
if(beacon_digi)
{
beacon_digi = false;
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
show_display(Config.callsign, "Beacon to HF...");
2020-11-07 23:07:43 +00:00
logPrintD("[" + timeClient.getFormattedTime() + "] ");
logPrintlnD(BeaconMsg->encode());
lora_aprs.sendMessage(BeaconMsg);
2020-11-07 23:07:43 +00:00
logPrintlnD("finished TXing...");
show_display(Config.callsign, "Standby...");
}
if(beacon_aprs_is)
{
beacon_aprs_is = false;
setup_display(); secondsSinceDisplay = 0; display_is_on = true;
2020-11-07 23:07:43 +00:00
show_display(Config.callsign, "Beacon to APRS-IS Server...");
logPrintD("[" + timeClient.getFormattedTime() + "] ");
logPrintlnD(BeaconMsg->encode());
aprs_is->sendMessage(BeaconMsg);
show_display(Config.callsign, "Standby...");
}
2020-07-22 20:21:54 +00:00
}
void load_config()
{
ConfigurationManagement confmg("/is-cfg.json");
Config = confmg.readConfiguration();
if(Config.callsign == "NOCALL-10")
2020-07-22 20:21:54 +00:00
{
2020-11-07 23:07:43 +00:00
logPrintlnE("You have to change your settings in 'data/is-cfg.json' and upload it via \"Upload File System image\"!");
2020-07-22 20:21:54 +00:00
show_display("ERROR", "You have to change your settings in 'data/is-cfg.json' and upload it via \"Upload File System image\"!");
while (true)
{}
2020-03-18 18:49:59 +00:00
}
2020-12-03 20:21:11 +00:00
#ifndef ETH_BOARD
if(Config.aprs_is.active && !Config.wifi.active)
{
2020-11-07 23:07:43 +00:00
logPrintlnE("You have to activate Wifi for APRS IS to work, please check your settings!");
show_display("ERROR", "You have to activate Wifi for APRS IS to work, please check your settings!");
while (true)
{}
}
2020-12-03 20:21:11 +00:00
#endif
if(KEY_BUILTIN != 0 && Config.display.overwritePin == 0)
{
Config.display.overwritePin = KEY_BUILTIN;
}
2020-11-07 23:07:43 +00:00
logPrintlnI("Configuration loaded!");
2020-03-18 18:49:59 +00:00
}
2020-04-03 21:32:10 +00:00
void setup_wifi()
{
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
WiFi.setHostname(Config.callsign.c_str());
for(Configuration::Wifi::AP ap : Config.wifi.APs)
{
2020-11-07 23:07:43 +00:00
logPrintD("Looking for AP: ");
logPrintlnD(ap.SSID);
WiFiMulti.addAP(ap.SSID.c_str(), ap.password.c_str());
}
2020-11-07 23:07:43 +00:00
logPrintlnI("Waiting for WiFi");
2020-04-03 21:32:10 +00:00
show_display("INFO", "Waiting for WiFi");
while(WiFiMulti.run() != WL_CONNECTED)
{
show_display("INFO", "Waiting for WiFi", "....");
delay(500);
}
2020-11-07 23:07:43 +00:00
logPrintlnI("WiFi connected");
logPrintD("IP address: ");
logPrintlnD(WiFi.localIP().toString());
2020-04-03 21:32:10 +00:00
show_display("INFO", "WiFi connected", "IP: ", WiFi.localIP().toString(), 2000);
}
2020-12-03 20:21:11 +00:00
void WiFiEvent(WiFiEvent_t event)
{
switch (event) {
case SYSTEM_EVENT_ETH_START:
Serial.println("ETH Started");
//set eth hostname here
ETH.setHostname("esp32-ethernet");
break;
case SYSTEM_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
case SYSTEM_EVENT_ETH_GOT_IP:
Serial.print("ETH MAC: ");
Serial.print(ETH.macAddress());
Serial.print(", IPv4: ");
Serial.print(ETH.localIP());
if (ETH.fullDuplex()) {
Serial.print(", FULL_DUPLEX");
}
Serial.print(", ");
Serial.print(ETH.linkSpeed());
Serial.println("Mbps");
eth_connected = true;
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
case SYSTEM_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
default:
break;
}
}
void setup_eth()
{
WiFi.onEvent(WiFiEvent);
pinMode(NRST, OUTPUT);
digitalWrite(NRST, 0);
delay(200);
digitalWrite(NRST, 1);
delay(200);
digitalWrite(NRST, 0);
delay(200);
digitalWrite(NRST, 1);
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK);
}
2020-04-03 21:32:10 +00:00
void setup_ota()
{
ArduinoOTA
.onStart([]()
{
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
Serial.println("Start updating " + type);
show_display("OTA UPDATE", "Start update", type);
})
.onEnd([]()
{
Serial.println();
Serial.println("End");
})
.onProgress([](unsigned int progress, unsigned int total)
{
Serial.print("Progress: ");
Serial.print(progress / (total / 100));
Serial.println("%");
show_display("OTA UPDATE", "Progress: ", String(progress / (total / 100)) + "%");
})
.onError([](ota_error_t error) {
Serial.print("Error[");
Serial.print(error);
Serial.print("]: ");
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.setHostname(Config.callsign.c_str());
2020-04-03 21:32:10 +00:00
ArduinoOTA.begin();
2020-11-07 23:07:43 +00:00
logPrintlnI("OTA init done!");
2020-04-03 21:32:10 +00:00
}
void setup_lora()
{
2020-10-31 23:42:20 +00:00
lora_aprs.setRxFrequency(Config.lora.frequencyRx);
lora_aprs.setTxFrequency(Config.lora.frequencyTx);
if (!lora_aprs.begin(lora_aprs.getRxFrequency()))
2020-06-04 20:27:44 +00:00
{
2020-11-07 23:07:43 +00:00
logPrintlnE("Starting LoRa failed!");
2020-04-03 21:32:10 +00:00
show_display("ERROR", "Starting LoRa failed!");
while (1);
}
lora_aprs.setTxPower(Config.lora.power);
lora_aprs.setSpreadingFactor(Config.lora.spreadingFactor);
lora_aprs.setSignalBandwidth(Config.lora.signalBandwidth);
lora_aprs.setCodingRate4(Config.lora.codingRate4);
2020-11-07 23:07:43 +00:00
logPrintlnI("LoRa init done!");
2020-04-03 21:32:10 +00:00
show_display("INFO", "LoRa init done!", 2000);
BeaconMsg = std::shared_ptr<APRSMessage>(new APRSMessage());
BeaconMsg->setSource(Config.callsign);
BeaconMsg->setDestination("APLG0");
String lat = create_lat_aprs(Config.beacon.positionLatitude);
String lng = create_long_aprs(Config.beacon.positionLongitude);
BeaconMsg->getAPRSBody()->setData(String("=") + lat + "I" + lng + "&" + Config.beacon.message);
2020-04-03 21:32:10 +00:00
}
void setup_ntp()
{
timeClient.begin();
while(!timeClient.forceUpdate())
2020-04-03 21:32:10 +00:00
{
logPrintlnW("NTP Client force update issue! Waiting 1 sek...");
show_display("WARN", "NTP Client force update issue! Waiting 1 sek...", 1000);
2020-04-03 21:32:10 +00:00
}
2020-11-07 23:07:43 +00:00
logPrintlnI("NTP Client init done!");
2020-04-03 21:32:10 +00:00
show_display("INFO", "NTP Client init done!", 2000);
}
2020-07-22 20:21:54 +00:00
void setup_aprs_is()
{
aprs_is = new APRS_IS(Config.callsign, Config.aprs_is.password , "ESP32-APRS-IS", "0.1");
2020-07-22 20:21:54 +00:00
}
void IRAM_ATTR onTimer()
{
portENTER_CRITICAL_ISR(&timerMux);
secondsSinceLastAPRSISBeacon++;
secondsSinceLastDigiBeacon++;
secondsSinceStartup++;
secondsSinceDisplay++;
portEXIT_CRITICAL_ISR(&timerMux);
}
void setup_timer()
{
timer = timerBegin(0, 80, true);
timerAlarmWrite(timer, 1000000, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmEnable(timer);
}
2020-11-03 22:35:19 +00:00
void setup_ftp()
{
2020-11-03 23:15:46 +00:00
if(!Config.ftp.active)
{
return;
}
for(Configuration::Ftp::User user : Config.ftp.users)
{
2020-11-07 23:07:43 +00:00
logPrintD("Adding user to FTP Server: ");
logPrintlnD(user.name);
2020-11-03 23:15:46 +00:00
ftpServer.addUser(user.name, user.password);
}
2020-11-03 22:35:19 +00:00
ftpServer.addFilesystem("SPIFFS", &SPIFFS);
ftpServer.begin();
2020-11-07 23:07:43 +00:00
logPrintlnI("FTP Server init done!");
2020-11-03 22:35:19 +00:00
}
String create_lat_aprs(double lat)
{
char str[20];
char n_s = 'N';
if(lat < 0)
{
n_s = 'S';
}
lat = std::abs(lat);
sprintf(str, "%02d%05.2f%c", (int)lat, (lat - (double)((int)lat)) * 60.0, n_s);
String lat_str(str);
return lat_str;
}
String create_long_aprs(double lng)
{
char str[20];
char e_w = 'E';
if(lng < 0)
{
e_w = 'W';
}
lng = std::abs(lng);
sprintf(str, "%03d%05.2f%c", (int)lng, (lng - (double)((int)lng)) * 60.0, e_w);
String lng_str(str);
return lng_str;
}