big update:

* add better timer functions
* add better configuration
* add DIGI mode
This commit is contained in:
Peter Buchegger 2020-10-12 00:09:34 +02:00
parent 1cbe124b6c
commit 7c23c2f1f4
4 changed files with 346 additions and 239 deletions

View file

@ -1,24 +1,36 @@
{
"Wifi":
"version":1,
"callsign":"NOCALL-10",
"wifi":
{
"Name":"",
"Password":""
"active":false,
"AP": [
{ "SSID":"", "password":"" }
]
},
"IS":
"beacon":
{
"Call":"NOCALL-10",
"Password":"",
"Server":"austria.aprs2.net",
"Port":14580
"message":"LoRa iGATE & Digi, Info: github.com/peterus/LoRa_APRS_iGate",
"position":
{
"latitude":0.000000,
"longitude":0.000000
}
},
"Beacon":
"aprs_is":
{
"Message":"LoRa IGATE (RX only), Info: github.com/peterus/LoRa_APRS_iGate",
"Pos":
{
"Lat":"4819.82N",
"Long":"01418.68E"
"active":false,
"password":"",
"server":"euro.aprs2.net",
"port":14580,
"beacon":true,
"beacon_timeout":15
},
"Timeout":15
"digi":
{
"active":false,
"forward_timeout":5,
"beacon":true,
"beacon_timeout":30
}
}

View file

@ -1,3 +1,5 @@
#include <map>
#include <Arduino.h>
#include <WiFiMulti.h>
#include <NTPClient.h>
@ -10,24 +12,29 @@
#include "pins.h"
#include "settings.h"
#include "display.h"
#include "power_management.h"
#include "configuration.h"
#if defined(ARDUINO_T_Beam) && !defined(ARDUINO_T_Beam_V0_7)
#include "power_management.h"
PowerManagement powerManagement;
#endif
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t * timer = NULL;
volatile uint secondsSinceLastAPRSISBeacon = 0;
volatile uint secondsSinceLastDigiBeacon = 0;
volatile uint secondsSinceStartup = 0;
WiFiMulti WiFiMulti;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, 60*60);
Configuration * Config = 0;
Configuration Config;
APRS_IS * aprs_is = 0;
#if defined(ARDUINO_T_Beam) && !defined(ARDUINO_T_Beam_V0_7)
PowerManagement powerManagement;
#endif
LoRa_APRS lora_aprs;
std::shared_ptr<APRSMessage> BeaconMsg;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t * timer = NULL;
volatile bool timerTick = false;
int next_update = -1;
String create_lat_aprs(double lat);
String create_long_aprs(double lng);
void load_config();
void setup_wifi();
@ -37,7 +44,7 @@ void setup_ntp();
void setup_aprs_is();
void setup_timer();
String BeaconMsg;
std::map<uint, std::shared_ptr<APRSMessage>> lastMessages;
// cppcheck-suppress unusedFunction
void setup()
@ -60,47 +67,58 @@ void setup()
setup_display();
delay(500);
Serial.println("[INFO] LoRa APRS iGate by OE5BPA (Peter Buchegger)");
show_display("OE5BPA", "LoRa APRS iGate", "by Peter Buchegger", 3000);
Serial.println("[INFO] LoRa APRS iGate & Digi by OE5BPA (Peter Buchegger)");
show_display("OE5BPA", "LoRa APRS iGate & Digi", "by Peter Buchegger", 3000);
load_config();
setup_wifi();
setup_ota();
if(Config.wifi.active) setup_wifi();
if(Config.wifi.active) setup_ota();
setup_lora();
setup_ntp();
setup_aprs_is();
if(Config.wifi.active) setup_ntp();
if(Config.aprs_is.active) setup_aprs_is();
setup_timer();
delay(500);
Serial.println("setup done...");
}
// cppcheck-suppress unusedFunction
void loop()
{
if(timerTick)
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);
timerTick = false;
secondsSinceLastAPRSISBeacon -= (Config.aprs_is.beaconTimeout*60);
portEXIT_CRITICAL(&timerMux);
next_update--;
beacon_aprs_is = true;
}
timeClient.update();
ArduinoOTA.handle();
if(WiFiMulti.run() != WL_CONNECTED)
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;
}
if(Config.wifi.active) ArduinoOTA.handle();
if(Config.wifi.active && WiFiMulti.run() != WL_CONNECTED)
{
Serial.println("[ERROR] WiFi not connected!");
show_display("ERROR", "WiFi not connected!");
delay(1000);
return;
}
if(!aprs_is->connected())
if(Config.aprs_is.active && !aprs_is->connected())
{
Serial.print("[INFO] connecting to server: ");
Serial.print(Config->getIsServer());
Serial.print(Config.aprs_is.server);
Serial.print(" on port: ");
Serial.println(Config->getIsPort());
Serial.println(Config.aprs_is.port);
show_display("INFO", "Connecting to server");
if(!aprs_is->connect(Config->getIsServer(), Config->getIsPort()))
if(!aprs_is->connect(Config.aprs_is.server, Config.aprs_is.port))
{
Serial.println("[ERROR] Connection failed.");
Serial.println("[INFO] Waiting 5 seconds before retrying...");
@ -110,30 +128,17 @@ void loop()
}
Serial.println("[INFO] Connected to server!");
}
if(next_update < 0)
{
show_display(Config->getIsCall(), "Beacon to Server...");
Serial.print("[" + timeClient.getFormattedTime() + "] ");
Serial.print(BeaconMsg);
aprs_is->sendMessage(BeaconMsg);
next_update = Config->getBeaconTimeout() * 60;
}
if(aprs_is->available() > 0)
if(Config.aprs_is.active && aprs_is->available() > 0)
{
String str = aprs_is->getMessage();
Serial.print("[" + timeClient.getFormattedTime() + "] ");
Serial.println(str);
#ifdef SEND_MESSAGES_FROM_IS_TO_LORA
std::shared_ptr<APRSMessage> msg = std::shared_ptr<APRSMessage>(new APRSMessage());
msg->decode(str);
lora_aprs.sendMessage(msg);
#endif
}
if(lora_aprs.hasMessage())
{
std::shared_ptr<APRSMessage> msg = lora_aprs.getMessage();
show_display(Config->getIsCall(), timeClient.getFormattedTime() + " LoRa", "RSSI: " + String(lora_aprs.getMessageRssi()) + ", SNR: " + String(lora_aprs.getMessageSnr()), msg->toString());
show_display(Config.callsign, timeClient.getFormattedTime() + " LoRa", "RSSI: " + String(lora_aprs.getMessageRssi()) + ", SNR: " + String(lora_aprs.getMessageSnr()), msg->toString());
Serial.print("[" + timeClient.getFormattedTime() + "] ");
Serial.print(" Received packet '");
Serial.print(msg->toString());
@ -142,32 +147,122 @@ void loop()
Serial.print(" and SNR ");
Serial.println(lora_aprs.getMessageSnr());
if(Config.aprs_is.active)
{
aprs_is->sendMessage(msg->encode());
}
static int _next_update = 0;
if(next_update != _next_update)
if(Config.digi.active)
{
show_display(Config->getIsCall(), "Time to next beaconing: " + String(next_update));
if(msg->getSource().indexOf(Config.callsign) != -1)
{
Serial.print("Message already received as repeater: '");
Serial.print(msg->toString());
Serial.print("' with RSSI ");
Serial.print(lora_aprs.getMessageRssi());
Serial.print(" and SNR ");
Serial.println(lora_aprs.getMessageSnr());
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())
{
show_display(Config.callsign, "RSSI: " + String(lora_aprs.getMessageRssi()) + ", SNR: " + String(lora_aprs.getMessageSnr()), msg->toString(), 0);
Serial.print("Received packet '");
Serial.print(msg->toString());
Serial.print("' with RSSI ");
Serial.print(lora_aprs.getMessageRssi());
Serial.print(" and SNR ");
Serial.println(lora_aprs.getMessageSnr());
msg->setPath(String(Config.callsign) + "*");
lora_aprs.sendMessage(msg);
lastMessages.insert({secondsSinceStartup, msg});
}
else
{
Serial.print("Message already received (timeout): '");
Serial.print(msg->toString());
Serial.print("' with RSSI ");
Serial.print(lora_aprs.getMessageRssi());
Serial.print(" and SNR ");
Serial.println(lora_aprs.getMessageSnr());
}
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
{
iter++;
}
}
}
if(beacon_digi)
{
beacon_digi = false;
show_display(Config.callsign, "Beacon to HF...");
Serial.print("[" + timeClient.getFormattedTime() + "] ");
Serial.print(BeaconMsg->encode());
lora_aprs.sendMessage(BeaconMsg);
Serial.println("finished TXing...");
}
if(beacon_aprs_is)
{
beacon_aprs_is = false;
show_display(Config.callsign, "Beacon to APRS IS Server...");
Serial.print("[" + timeClient.getFormattedTime() + "] ");
Serial.print(BeaconMsg->encode());
aprs_is->sendMessage(BeaconMsg);
}
}
void load_config()
{
Config = new Configuration("/is-cfg.json");
if(Config->getIsCall() == "NOCALL-10" || Config->getWifiName() == "")
ConfigurationManagement confmg("/is-cfg.json");
Config = confmg.readConfiguration();
if(Config.callsign == "NOCALL-10")
{
Serial.println("[ERROR] You have to change your settings in 'data/is-cfg.json' and upload it via \"Upload File System image\"!");
show_display("ERROR", "You have to change your settings in 'data/is-cfg.json' and upload it via \"Upload File System image\"!");
while (true)
{}
}
if(Config.aprs_is.active && !Config.wifi.active)
{
Serial.println("[ERROR] 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)
{}
}
}
void setup_wifi()
{
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
WiFi.setHostname(Config->getIsCall().c_str());
WiFiMulti.addAP(Config->getWifiName().c_str(), Config->getWifiPassword().c_str());
WiFi.setHostname(Config.callsign.c_str());
for(Configuration::Wifi::AP ap : Config.wifi.APs)
{
WiFiMulti.addAP(ap.SSID.c_str(), ap.password.c_str());
}
Serial.print("[INFO] Waiting for WiFi");
show_display("INFO", "Waiting for WiFi");
while(WiFiMulti.run() != WL_CONNECTED)
@ -218,15 +313,13 @@ void setup_ota()
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.setHostname(Config->getIsCall().c_str());
ArduinoOTA.setHostname(Config.callsign.c_str());
ArduinoOTA.begin();
}
void setup_lora()
{
Serial.print("[INFO] frequency: ");
Serial.println(LORA_RX_FREQUENCY);
lora_aprs.tx_frequency = LORA_RX_FREQUENCY;
if (!lora_aprs.begin())
{
Serial.println("[ERROR] Starting LoRa failed!");
@ -235,6 +328,13 @@ void setup_lora()
}
Serial.println("[INFO] LoRa init done!");
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);
}
void setup_ntp()
@ -251,19 +351,15 @@ void setup_ntp()
void setup_aprs_is()
{
aprs_is = new APRS_IS(Config->getIsCall(), Config->getIsPassword() , "ESP32-APRS-IS", "0.1");
APRSMessage msg;
msg.setSource(Config->getIsCall());
msg.setDestination("APLG0");
msg.getAPRSBody()->setData(String("=") + Config->getBeaconPosLat() + "I" + Config->getBeaconPosLong() + "&" + Config->getBeaconMessage());
BeaconMsg = msg.encode();
aprs_is = new APRS_IS(Config.callsign, Config.aprs_is.password , "ESP32-APRS-IS", "0.1");
}
void IRAM_ATTR onTimer()
{
portENTER_CRITICAL_ISR(&timerMux);
timerTick = true;
secondsSinceLastAPRSISBeacon++;
secondsSinceLastDigiBeacon++;
secondsSinceStartup++;
portEXIT_CRITICAL_ISR(&timerMux);
}
@ -274,3 +370,31 @@ void setup_timer()
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmEnable(timer);
}
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;
}

View file

@ -4,8 +4,8 @@
#include "settings.h"
Configuration::Configuration(String FilePath)
: mFilePath(FilePath), mData(1024)
ConfigurationManagement::ConfigurationManagement(String FilePath)
: mFilePath(FilePath)
{
if(!SPIFFS.begin(true))
{
@ -17,150 +17,55 @@ Configuration::Configuration(String FilePath)
return;
}
}
if(SPIFFS.exists(mFilePath))
if(!SPIFFS.exists(mFilePath))
{
readFile();
}
else
{
mData["Wifi"]["Name"] = WIFI_NAME;
mData["Wifi"]["Password"] = WIFI_KEY;
mData["IS"]["Call"] = USER;
mData["IS"]["Password"] = PASS;
mData["IS"]["Server"] = SERVER;
mData["IS"]["Port"] = PORT;
mData["Beacon"]["Message"] = BEACON_MESSAGE;
mData["Beacon"]["Pos"]["Lat"] = BEACON_LAT_POS;
mData["Beacon"]["Pos"]["Long"] = BEACON_LONG_POS;
mData["Beacon"]["Timeout"] = BEACON_TIMEOUT;
writeFile();
Configuration conf;
writeConfiguration(conf);
}
}
String Configuration::getWifiName() const
{
return mData["Wifi"]["Name"];
}
// cppcheck-suppress unusedFunction
void Configuration::setWifiName(String WifiName)
{
mData["Wifi"]["Name"] = WifiName;
}
String Configuration::getWifiPassword() const
{
return mData["Wifi"]["Password"];
}
// cppcheck-suppress unusedFunction
void Configuration::setWifiPassword(String WifiPassword)
{
mData["Wifi"]["Password"] = WifiPassword;
}
String Configuration::getIsCall() const
{
return mData["IS"]["Call"];
}
// cppcheck-suppress unusedFunction
void Configuration::setIsCall(String IsCall)
{
mData["IS"]["Call"] = IsCall;
}
String Configuration::getIsPassword() const
{
return mData["IS"]["Password"];
}
// cppcheck-suppress unusedFunction
void Configuration::setIsPassword(String IsPassword)
{
mData["IS"]["Password"] = IsPassword;
}
String Configuration::getIsServer() const
{
return mData["IS"]["Server"];
}
// cppcheck-suppress unusedFunction
void Configuration::setIsServer(String IsServer)
{
mData["IS"]["Server"] = IsServer;
}
int Configuration::getIsPort() const
{
return mData["IS"]["Port"];
}
// cppcheck-suppress unusedFunction
void Configuration::setIsPort(int IsPort)
{
mData["IS"]["Port"] = IsPort;
}
String Configuration::getBeaconMessage() const
{
return mData["Beacon"]["Message"];
}
// cppcheck-suppress unusedFunction
void Configuration::setBeaconMessage(String BeaconMessage)
{
mData["Beacon"]["Message"] = BeaconMessage;
}
String Configuration::getBeaconPosLat() const
{
return mData["Beacon"]["Pos"]["Lat"];
}
// cppcheck-suppress unusedFunction
void Configuration::setBeaconPosLat(String BeaconPosLat)
{
mData["Beacon"]["Pos"]["Lat"] = BeaconPosLat;
}
String Configuration::getBeaconPosLong() const
{
return mData["Beacon"]["Pos"]["Long"];
}
// cppcheck-suppress unusedFunction
void Configuration::setBeaconPosLong(String BeaconPosLong)
{
mData["Beacon"]["Pos"]["Long"] = BeaconPosLong;
}
int Configuration::getBeaconTimeout() const
{
return mData["Beacon"]["Timeout"];
}
// cppcheck-suppress unusedFunction
void Configuration::setBeaconTimeout(int BeaconTimeout)
{
mData["Beacon"]["Timeout"] = BeaconTimeout;
}
void Configuration::readFile()
Configuration ConfigurationManagement::readConfiguration()
{
File file = SPIFFS.open(mFilePath);
if(!file)
{
Serial.println("Failed to open file for reading...");
return;
return Configuration();
}
deserializeJson(mData, file);
DynamicJsonDocument data(1024);
deserializeJson(data, file);
file.close();
Configuration conf;
conf.version = data["version"];
conf.callsign = data["callsign"].as<String>();
conf.wifi.active = data["wifi"]["active"];
JsonArray aps = data["wifi"]["AP"].as<JsonArray>();
for(JsonVariant v : aps)
{
Configuration::Wifi::AP ap;
ap.SSID = v["SSID"].as<String>();
ap.password = v["password"].as<String>();
conf.wifi.APs.push_back(ap);
}
conf.beacon.message = data["beacon"]["message"].as<String>();
conf.beacon.positionLatitude = data["beacon"]["position"]["latitude"];
conf.beacon.positionLongitude = data["beacon"]["position"]["longitude"];
conf.aprs_is.active = data["aprs_is"]["active"];
conf.aprs_is.password = data["aprs_is"]["password"].as<String>();
conf.aprs_is.server = data["aprs_is"]["server"].as<String>();
conf.aprs_is.port = data["aprs_is"]["port"];
conf.aprs_is.beacon = data["aprs_is"]["beacon"];
conf.aprs_is.beaconTimeout = data["aprs_is"]["beacon_timeout"];
conf.digi.active = data["digi"]["active"];
conf.digi.forwardTimeout = data["digi"]["forward_timeout"];
conf.digi.beacon = data["digi"]["beacon"];
conf.digi.beaconTimeout = data["digi"]["beacon_timeout"];
return conf;
}
void Configuration::writeFile()
void ConfigurationManagement::writeConfiguration(Configuration conf)
{
File file = SPIFFS.open(mFilePath, "w");
if(!file)
@ -168,8 +73,35 @@ void Configuration::writeFile()
Serial.println("Failed to open file for writing...");
return;
}
serializeJson(mData, file);
serializeJson(mData, Serial);
DynamicJsonDocument data(1024);
data["version"] = conf.version;
data["callsign"] = conf.callsign;
data["wifi"]["active"] = conf.wifi.active;
JsonArray aps = data["wifi"]["AP"].to<JsonArray>();
for(Configuration::Wifi::AP ap : conf.wifi.APs)
{
JsonVariant v;
v["SSID"] = ap.SSID;
v["password"] = ap.password;
aps.add(v);
}
data["beacon"]["message"] = conf.beacon.message;
data["beacon"]["position"]["latitude"] = conf.beacon.positionLatitude;
data["beacon"]["position"]["longitude"] = conf.beacon.positionLongitude;
data["aprs_is"]["active"] = conf.aprs_is.active;
data["aprs_is"]["password"] = conf.aprs_is.password;
data["aprs_is"]["server"] = conf.aprs_is.server;
data["aprs_is"]["port"] = conf.aprs_is.port;
data["aprs_is"]["beacon"] = conf.aprs_is.beacon;
data["aprs_is"]["beacon_timeout"] = conf.aprs_is.beaconTimeout;
data["digi"]["active"] = conf.digi.active;
data["digi"]["forward_timeout"] = conf.digi.forwardTimeout;
data["digi"]["beacon"] = conf.digi.beacon;
data["digi"]["beacon_timeout"] = conf.digi.beaconTimeout;
serializeJson(data, file);
serializeJson(data, Serial);
Serial.println();
file.close();
}

View file

@ -1,45 +1,84 @@
#ifndef CONFIGURATION_H_
#define CONFIGURATION_H_
#include <list>
#include <Arduino.h>
#include <ArduinoJson.h>
class Configuration
{
public:
explicit Configuration(String FilePath);
class Wifi
{
public:
class AP
{
public:
String SSID;
String password;
};
void readFile();
void writeFile();
Wifi() : active(false) {}
String getWifiName() const;
void setWifiName(String WifiName);
String getWifiPassword() const;
void setWifiPassword(String WifiPassword);
bool active;
std::list<AP> APs;
};
String getIsCall() const;
void setIsCall(String IsCall);
String getIsPassword() const;
void setIsPassword(String IsPassword);
String getIsServer() const;
void setIsServer(String IsServer);
int getIsPort() const;
void setIsPort(int IsPort);
class Beacon
{
public:
Beacon() : message("LoRa iGATE & Digi, Info: github.com/peterus/LoRa_APRS_iGate") {}
String getBeaconMessage() const;
void setBeaconMessage(String BeaconMessage);
String getBeaconPosLat() const;
void setBeaconPosLat(String BeaconPosLat);
String getBeaconPosLong() const;
void setBeaconPosLong(String BeaconPosLong);
int getBeaconTimeout() const;
void setBeaconTimeout(int BeaconTimeout);
String message;
double positionLatitude;
double positionLongitude;
};
class APRS_IS
{
public:
APRS_IS() : active(false), server("euro.aprs2.net"), port(14580), beacon(true), beaconTimeout(15) {}
bool active;
String password;
String server;
int port;
bool beacon;
int beaconTimeout;
};
class Digi
{
public:
Digi() : active(false), forwardTimeout(5), beacon(true), beaconTimeout(30) {}
bool active;
int forwardTimeout;
bool beacon;
int beaconTimeout;
};
Configuration() : version(1), callsign("NOCALL-10") {};
int version;
String callsign;
Wifi wifi;
Beacon beacon;
APRS_IS aprs_is;
Digi digi;
};
class ConfigurationManagement
{
public:
explicit ConfigurationManagement(String FilePath);
Configuration readConfiguration();
void writeConfiguration(Configuration conf);
private:
const String mFilePath;
DynamicJsonDocument mData;
};
#endif