LoRa_APRS_Tracker111/src/LoRa_APRS_Tracker.cpp

1121 lines
43 KiB
C++

#ifdef ESP32
#include <esp_bt.h>
#endif
#include <APRS-Decoder.h>
#include <Arduino.h>
#include <LoRa.h>
#include <OneButton.h>
#include <TimeLib.h>
#include <TinyGPS++.h>
#include <WiFi.h>
#include <logger.h>
#include "SPIFFS.h"
#include <vector>
#include "configuration.h"
#include "display.h"
#include "pins.h"
#include "power_management.h"
#define VERSION "2023.06.02"
logging::Logger logger;
String configurationFilePath = "/tracker_config.json";
Configuration Config(configurationFilePath);
static int myBeaconsIndex = 0;
int myBeaconsSize = Config.beacons.size();
Beacon *currentBeacon = &Config.beacons[myBeaconsIndex];
PowerManagement powerManagement;
OneButton userButton = OneButton(BUTTON_PIN, true, true);
HardwareSerial neo6m_gps(1);
TinyGPSPlus gps;
void validateConfigFile();
void setup_lora();
void setup_gps();
char *ax25_base91enc(char *s, uint8_t n, uint32_t v);
String createDateString(time_t t);
String createTimeString(time_t t);
String getSmartBeaconState();
String padding(unsigned int number, unsigned int width);
static int menuDisplay = 0;
static bool displayEcoMode = Config.displayEcoMode;
static uint32_t displayTime = millis();
static bool displayState = true;
static bool send_update = true;
static String lastHeardTracker = "NONE";
static int numAPRSMessages = 0;
static int messagesIterator = 0;
static String lastMessageAPRS = "";
static bool noMessageWarning = false;
static bool statusAfterBootState = true;
String firstNearTracker = "";
String secondNearTracker = "";
String thirdNearTracker = "";
String fourthNearTracker = "";
std::vector<String> loadedAPRSMessages;
static uint32_t lastDeleteListenedTracker = millis();
void loadNumMessages() {
if(!SPIFFS.begin(true)){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
File fileToReadAPRS = SPIFFS.open("/aprsMessages.txt");
if(!fileToReadAPRS){
Serial.println("Failed to open APRS_Msg for reading");
return;
}
std::vector<String> v1;
while (fileToReadAPRS.available()) {
v1.push_back(fileToReadAPRS.readStringUntil('\n'));
}
fileToReadAPRS.close();
numAPRSMessages = 0;
for (String s1 : v1) {
numAPRSMessages++;
}
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "Number of APRS Messages : %s", String(numAPRSMessages));
}
void setup_gps() {
neo6m_gps.begin(9600, SERIAL_8N1, GPS_TX, GPS_RX);
}
void setup_lora() {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Set SPI pins!");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "Set LoRa pins!");
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
long freq = Config.loramodule.frequency;
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "frequency: %d", freq);
if (!LoRa.begin(freq)) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "LoRa", "Starting LoRa failed!");
show_display("ERROR", "Starting LoRa failed!");
while (true) {
}
}
LoRa.setSpreadingFactor(Config.loramodule.spreadingFactor);
LoRa.setSignalBandwidth(Config.loramodule.signalBandwidth);
LoRa.setCodingRate4(Config.loramodule.codingRate4);
LoRa.enableCrc();
LoRa.setTxPower(Config.loramodule.power);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "LoRa", "LoRa init done!");
}
void loadMessagesFromMemory() {
File fileToRead;
noMessageWarning = false;
if (numAPRSMessages == 0) {
noMessageWarning = true;
} else {
loadedAPRSMessages.clear();
fileToRead = SPIFFS.open("/aprsMessages.txt");
}
if (noMessageWarning) {
show_display("__INFO____", "", "NO MESSAGES IN MEMORY", 1500);
} else {
if(!fileToRead){
Serial.println("Failed to open file for reading");
return;
}
while (fileToRead.available()) {
loadedAPRSMessages.push_back(fileToRead.readStringUntil('\n'));
}
fileToRead.close();
}
}
void deleteFile() {
if(!SPIFFS.begin(true)){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
SPIFFS.remove("/aprsMessages.txt");
}
void sendNewLoraPacket(String newPacket) {
LoRa.beginPacket();
LoRa.write('<');
LoRa.write(0xFF);
LoRa.write(0x01);
LoRa.write((const uint8_t *)newPacket.c_str(), newPacket.length());
LoRa.endPacket();
}
void sendMessage(String station, String textMessage) {
String messageToSend;
for(int i = station.length(); i < 9; i++) {
station += ' ';
}
messageToSend = currentBeacon->callsign + ">APLR01,WIDE1-1::" + station + ":" + textMessage;
if (textMessage.indexOf("ack")== 0) {
show_display("<<ACK Tx>>", 500);
} else if (station.indexOf("CD2RXU-15") == 0 && textMessage.indexOf("wrl")==0) {
show_display("<WEATHER>","", "--- Sending Query ---", 1000);
} else {
show_display("MSG Tx >>", "", messageToSend, 1000);
}
sendNewLoraPacket(messageToSend);
}
static void ButtonSinglePress() {
if (menuDisplay == 0) {
if (displayState) {
send_update = true;
} else {
display_toggle(true);
displayTime = millis();
displayState = true;
}
} else if (menuDisplay == 1) {
loadMessagesFromMemory();
if (noMessageWarning) {
menuDisplay = 1;
} else {
menuDisplay = 10;
}
} else if (menuDisplay == 2) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Loop", "%s", "wrl");
sendMessage("CD2RXU-15","wrl");
} else if (menuDisplay == 10) {
messagesIterator++;
if (messagesIterator == numAPRSMessages) {
menuDisplay = 1;
messagesIterator = 0;
} else {
menuDisplay = 10;
}
} else if (menuDisplay == 20) {
menuDisplay = 2;
} else if (menuDisplay == 3) {
show_display("__INFO____", "", "NOTHING YET ...", 1000);
}
}
static void ButtonLongPress() {
if (menuDisplay == 0) {
if(myBeaconsIndex >= (myBeaconsSize-1)) {
myBeaconsIndex = 0;
} else {
myBeaconsIndex++;
}
statusAfterBootState = true;
display_toggle(true);
displayTime = millis();
show_display("__INFO____", "", "CHANGING CALLSIGN ...", 1000);
} else if (menuDisplay == 1) {
deleteFile();
show_display("__INFO____", "", "ALL MESSAGES DELETED!", 2000);
loadNumMessages();
} else if (menuDisplay == 2) {
menuDisplay = 20;
} else if (menuDisplay == 3) {
if (!displayEcoMode) {
displayEcoMode = true;
show_display("__DISPLAY_", "", " ECO MODE -> ON", 1000);
} else {
displayEcoMode = false;
show_display("__DISPLAY_", "", " ECO MODE -> OFF", 1000);
}
}
}
static void ButtonDoublePress() {
display_toggle(true);
if (menuDisplay == 0) {
menuDisplay = 1;
} else if (menuDisplay == 1) {
menuDisplay = 2;
messagesIterator = 0;
} else if (menuDisplay == 2) {
menuDisplay = 3;
} else if (menuDisplay == 3 || menuDisplay == 20) {
menuDisplay = 0;
displayTime = millis();
}
}
void startingStatus() {
delay(2000);
sendNewLoraPacket(currentBeacon->callsign + ">" + Config.destination + "," + Config.path + ":>" + Config.defaultStatus);
statusAfterBootState = false;
}
void saveNewMessage(String typeMessage, String station, String gate, String newMessage) {
if (typeMessage == "APRS" && lastMessageAPRS != newMessage) {
File fileToAppendAPRS = SPIFFS.open("/aprsMessages.txt", FILE_APPEND);
if(!fileToAppendAPRS){
Serial.println("There was an error opening the file for appending");
return;
}
newMessage.trim();
if(!fileToAppendAPRS.println(station + "," + gate + "," + newMessage)){
Serial.println("File append failed");
}
lastMessageAPRS = newMessage;
numAPRSMessages++;
fileToAppendAPRS.close();
}
}
void orderListenedTrackersByDistance(String callsign, float distance, float course) {
String firstNearTrackerDistance, secondNearTrackerDistance, thirdNearTrackerDistance, fourthNearTrackerDistance, newTrackerInfo, firstNearTrackerCallsign, secondNearTrackerCallsign,thirdNearTrackerCallsign, fourthNearTrackerCallsign;
newTrackerInfo = callsign + "> " + String(distance,2) + "km " + String(int(course)) + "," + String(millis());
float firstDistance = 0.0;
float secondDistance = 0.0;
float thirdDistance = 0.0;
float fourthDistance = 0.0;
if (firstNearTracker != "") {
firstNearTrackerCallsign = firstNearTracker.substring(0,firstNearTracker.indexOf(">"));
firstNearTrackerDistance = firstNearTracker.substring(firstNearTracker.indexOf(">")+1,firstNearTracker.indexOf("km"));
firstDistance = firstNearTrackerDistance.toFloat();
}
if (secondNearTracker != "") {
secondNearTrackerCallsign = secondNearTracker.substring(0,secondNearTracker.indexOf(">"));
secondNearTrackerDistance = secondNearTracker.substring(secondNearTracker.indexOf(">")+1,secondNearTracker.indexOf("km"));
secondDistance = secondNearTrackerDistance.toFloat();
}
if (thirdNearTracker != "") {
thirdNearTrackerCallsign = thirdNearTracker.substring(0,thirdNearTracker.indexOf(">"));
thirdNearTrackerDistance = thirdNearTracker.substring(thirdNearTracker.indexOf(">")+1,thirdNearTracker.indexOf("km"));
thirdDistance = thirdNearTrackerDistance.toFloat();
}
if (fourthNearTracker != "") {
fourthNearTrackerCallsign = fourthNearTracker.substring(0,fourthNearTracker.indexOf(">"));
fourthNearTrackerDistance = fourthNearTracker.substring(fourthNearTracker.indexOf(">")+1,fourthNearTracker.indexOf("km"));
fourthDistance = fourthNearTrackerDistance.toFloat();
}
if (firstNearTracker == "" && secondNearTracker == "" && thirdNearTracker == "" && fourthNearTracker == "") {
firstNearTracker = newTrackerInfo;
} else if (firstNearTracker != "" && secondNearTracker == "" && thirdNearTracker == "" && fourthNearTracker == "") {
if (callsign != firstNearTrackerCallsign) {
if (distance < firstDistance) {
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else {
secondNearTracker = newTrackerInfo;
}
} else {
if (distance != firstDistance) {
firstNearTracker = newTrackerInfo;
}
}
} else if (firstNearTracker != "" && secondNearTracker != "" && thirdNearTracker == "" && fourthNearTracker == "") {
if (callsign != firstNearTrackerCallsign && callsign != secondNearTrackerCallsign) {
if (distance < firstDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else if (distance < secondDistance && distance >= firstDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance >= secondDistance) {
thirdNearTracker = newTrackerInfo;
}
} else {
if (callsign == firstNearTrackerCallsign) {
if (distance != firstDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > secondDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else {
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == secondNearTrackerCallsign) {
if (distance != secondDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance < firstDistance) {
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else {
secondNearTracker = newTrackerInfo;
}
}
}
}
} else if (firstNearTracker != "" && secondNearTracker != "" && thirdNearTracker != "" && fourthNearTracker == "") {
if (callsign != firstNearTrackerCallsign && callsign != secondNearTrackerCallsign && callsign != thirdNearTrackerCallsign) {
if (distance < firstDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else if (distance >= firstDistance && distance < secondDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance >= secondDistance && distance < thirdDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance >= thirdDistance) {
fourthNearTracker = newTrackerInfo;
}
} else {
if (callsign == firstNearTrackerCallsign) {
if (distance != firstDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > thirdDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance <= thirdDistance && distance > secondDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance <= secondDistance) {
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == secondNearTrackerCallsign) {
if (distance != secondDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > thirdDistance) {
secondNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance <= thirdDistance && distance > firstDistance) {
secondNearTracker = newTrackerInfo;
} else if (distance <= firstDistance) {
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == thirdNearTrackerCallsign) {
if (distance != thirdDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance <= firstDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else if (distance > firstDistance && distance <= secondDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance > secondDistance) {
thirdNearTracker = newTrackerInfo;
}
}
}
}
} else if (firstNearTracker != "" && secondNearTracker != "" && thirdNearTracker != "" && fourthNearTracker != "") {
if (callsign != firstNearTrackerCallsign && callsign != secondNearTrackerCallsign && callsign != thirdNearTrackerCallsign && callsign != fourthNearTrackerCallsign) {
if (distance < firstDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
} else if (distance < secondDistance && distance >= firstDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance < thirdDistance && distance >= secondDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance < fourthDistance && distance >= thirdDistance) {
fourthNearTracker = newTrackerInfo;
}
} else {
if (callsign == firstNearTrackerCallsign) {
if (distance != firstDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > fourthDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = thirdNearTracker;
thirdNearTracker = fourthNearTracker;
fourthNearTracker = newTrackerInfo;
} else if (distance > thirdDistance && distance <= fourthDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance > secondDistance && distance <= thirdDistance) {
firstNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance <= secondDistance) {
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == secondNearTrackerCallsign) {
if (distance != secondDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > fourthDistance) {
secondNearTracker = thirdNearTracker;
thirdNearTracker = fourthNearTracker;
fourthNearTracker = newTrackerInfo;
} else if (distance > thirdDistance && distance <= fourthDistance) {
secondNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance > firstDistance && distance <= thirdDistance) {
secondNearTracker = newTrackerInfo;
} else if (distance <= firstDistance) {
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == thirdNearTrackerCallsign) {
if (distance != thirdDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > fourthDistance) {
thirdNearTracker = fourthNearTracker;
fourthNearTracker = newTrackerInfo;
} else if (distance > secondDistance && distance <= fourthDistance) {
thirdNearTracker = newTrackerInfo;
} else if (distance > firstDistance && distance <= secondDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance <= firstDistance) {
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
}
}
} else if (callsign == fourthNearTrackerCallsign) {
if (distance != fourthDistance) {
Serial.print("Distance Updated for : "); Serial.println(callsign);
if (distance > thirdDistance) {
fourthNearTracker = newTrackerInfo;
} else if (distance > secondDistance && distance <= thirdDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = newTrackerInfo;
} else if (distance > firstDistance && distance <= secondDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = newTrackerInfo;
} else if (distance <= firstDistance) {
fourthNearTracker = thirdNearTracker;
thirdNearTracker = secondNearTracker;
secondNearTracker = firstNearTracker;
firstNearTracker = newTrackerInfo;
}
}
}
}
}
}
void deleteListenedTrackersbyTime() {
String firstNearTrackermillis, secondNearTrackermillis, thirdNearTrackermillis, fourthNearTrackermillis;
uint32_t firstTrackermillis, secondTrackermillis, thirdTrackermillis, fourthTrackermillis;
if (firstNearTracker != "") {
firstNearTrackermillis = firstNearTracker.substring(firstNearTracker.indexOf(",")+1);
firstTrackermillis = firstNearTrackermillis.toInt();
if ((millis() - firstTrackermillis) > Config.listeningTrackerTime*60*1000) {
firstNearTracker = "";
}
}
if (secondNearTracker != "") {
secondNearTrackermillis = secondNearTracker.substring(secondNearTracker.indexOf(",")+1);
secondTrackermillis = secondNearTrackermillis.toInt();
if ((millis() - secondTrackermillis) > Config.listeningTrackerTime*60*1000) {
secondNearTracker = "";
}
}
if (thirdNearTracker != "") {
thirdNearTrackermillis = thirdNearTracker.substring(thirdNearTracker.indexOf(",")+1);
thirdTrackermillis = thirdNearTrackermillis.toInt();
if ((millis() - thirdTrackermillis) > Config.listeningTrackerTime*60*1000) {
thirdNearTracker = "";
}
}
if (fourthNearTracker != "") {
fourthNearTrackermillis = fourthNearTracker.substring(fourthNearTracker.indexOf(",")+1);
fourthTrackermillis = fourthNearTrackermillis.toInt();
if ((millis() - fourthTrackermillis) > Config.listeningTrackerTime*60*1000) {
fourthNearTracker = "";
}
}
if (thirdNearTracker == "") {
thirdNearTracker = fourthNearTracker;
fourthNearTracker = "";
}
if (secondNearTracker == "") {
secondNearTracker = thirdNearTracker;
thirdNearTracker = fourthNearTracker;
fourthNearTracker = "";
}
if (firstNearTracker == "") {
firstNearTracker = secondNearTracker;
secondNearTracker = thirdNearTracker;
thirdNearTracker = fourthNearTracker;
fourthNearTracker = "";
}
lastDeleteListenedTracker = millis();
}
void calculateDistanceCourse(String Callsign, double checkpointLatitude, double checkPointLongitude) {
double distanceKm = TinyGPSPlus::distanceBetween(gps.location.lat(), gps.location.lng(), checkpointLatitude, checkPointLongitude) / 1000.0;
double courseTo = TinyGPSPlus::courseTo(gps.location.lat(), gps.location.lng(), checkpointLatitude, checkPointLongitude);
deleteListenedTrackersbyTime();
orderListenedTrackersByDistance(Callsign, distanceKm, courseTo);
}
void decodeEncodedGPS(String packet, String sender) {
String GPSPacket = packet.substring(packet.indexOf(":!/")+3);
String encodedLatitude = GPSPacket.substring(0,4);
String encodedLongtitude = GPSPacket.substring(4,8);
int Y1 = int(encodedLatitude[0]);
int Y2 = int(encodedLatitude[1]);
int Y3 = int(encodedLatitude[2]);
int Y4 = int(encodedLatitude[3]);
float decodedLatitude = 90 - ((((Y1-33) * pow(91,3)) + ((Y2-33) * pow(91,2)) + ((Y3-33) * 91) + Y4-33) / 380926);
int X1 = int(encodedLongtitude[0]);
int X2 = int(encodedLongtitude[1]);
int X3 = int(encodedLongtitude[2]);
int X4 = int(encodedLongtitude[3]);
float decodedLongitude = -180 + ((((X1-33) * pow(91,3)) + ((X2-33) * pow(91,2)) + ((X3-33) * 91) + X4-33) / 190463);
Serial.print(sender);
Serial.print(" GPS : ");
Serial.print(decodedLatitude); Serial.print(" N ");
Serial.print(decodedLongitude);Serial.println(" E");
calculateDistanceCourse(sender, decodedLatitude, decodedLongitude);
}
void getReceivedGPS(String packet, String sender) {
String infoGPS;
if (packet.indexOf(":!") > 10) {
infoGPS = packet.substring(packet.indexOf(":!")+2);
} else if (packet.indexOf(":=") > 10) {
infoGPS = packet.substring(packet.indexOf(":=")+2);
}
String Latitude = infoGPS.substring(0,8);
String Longitude = infoGPS.substring(9,18);
float convertedLatitude, convertedLongitude;
String firstLatPart = Latitude.substring(0,2);
String secondLatPart = Latitude.substring(2,4);
String thirdLatPart = Latitude.substring(Latitude.indexOf(".")+1,Latitude.indexOf(".")+3);
String firstLngPart = Longitude.substring(0,3);
String secondLngPart = Longitude.substring(3,5);
String thirdLngPart = Longitude.substring(Longitude.indexOf(".")+1,Longitude.indexOf(".")+3);
convertedLatitude = firstLatPart.toFloat() + (secondLatPart.toFloat()/60) + (thirdLatPart.toFloat()/(60*100));
convertedLongitude = firstLngPart.toFloat() + (secondLngPart.toFloat()/60) + (thirdLngPart.toFloat()/(60*100));
String LatSign = String(Latitude[7]);
String LngSign = String(Longitude[8]);
if (LatSign == "S") {
convertedLatitude = -convertedLatitude;
}
if (LngSign == "W") {
convertedLongitude = -convertedLongitude;
}
Serial.print(sender);
Serial.print(" GPS : ");
Serial.print(convertedLatitude); Serial.print(" N ");
Serial.print(convertedLongitude);Serial.println(" E");
calculateDistanceCourse(sender, convertedLatitude, convertedLongitude);
}
void checkReceivedMessage(String packetReceived) {
Serial.println(packetReceived);
String Sender, AddresseeAndMessage, Addressee, receivedMessage, ackMessage, iGate;
if (packetReceived.substring(0,3) == "\x3c\xff\x01") { // its an APRS packet
Sender = packetReceived.substring(3,packetReceived.indexOf(">"));
if (Sender != currentBeacon->callsign) { // avoid listening yourself by digirepeating
if (packetReceived.indexOf("::") > 10) { // its a Message!
AddresseeAndMessage = packetReceived.substring(packetReceived.indexOf("::")+2);
Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":"));
Addressee.trim();
iGate = packetReceived.substring(packetReceived.indexOf("TCPIP,")+6,packetReceived.indexOf("::"));
if (Addressee == currentBeacon->callsign) { // its for me!
if (AddresseeAndMessage.indexOf("{")>0) { // ack?
ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1);
ackMessage.trim();
delay(4000);
sendMessage(Sender, ackMessage);
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{"));
} else {
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1);
}
//Serial.println(receivedMessage);
if (receivedMessage.indexOf("ping")==0 || receivedMessage.indexOf("Ping")==0 || receivedMessage.indexOf("PING")==0) {
delay(4000);
sendMessage(Sender, "pong, 73!");
}
if (Sender == "CD2RXU-15") {
if (receivedMessage.indexOf("WX")==0) { // WX = WeatherReport
Serial.println("Weather Report Received");
String wxCleaning = receivedMessage.substring(receivedMessage.indexOf("WX ")+3);
String place = wxCleaning.substring(0,wxCleaning.indexOf(","));
String placeCleaning = wxCleaning.substring(wxCleaning.indexOf(",")+1);
String summary = placeCleaning.substring(0,placeCleaning.indexOf(","));
String sumCleaning = placeCleaning.substring(placeCleaning.indexOf(",")+2);
String temperature = sumCleaning.substring(0,sumCleaning.indexOf("P"));
String tempCleaning = sumCleaning.substring(sumCleaning.indexOf("P")+1);
String pressure = tempCleaning.substring(0,tempCleaning.indexOf("H"));
String presCleaning = tempCleaning.substring(tempCleaning.indexOf("H")+1);
String humidity = presCleaning.substring(0,presCleaning.indexOf("W"));
String humCleaning = presCleaning.substring(presCleaning.indexOf("W")+1);
String windSpeed = humCleaning.substring(0,humCleaning.indexOf(","));
String windCleaning = humCleaning.substring(humCleaning.indexOf(",")+1);
String windDegrees = windCleaning.substring(windCleaning.indexOf(",")+1,windCleaning.indexOf("\n"));
String fifthLineWR = temperature + "C " + pressure + "hPa " + humidity +"%";
String sixthLineWR = "(wind " + windSpeed + "m/s " + windDegrees + "deg)";
show_display("<WEATHER>", "From --> " + Sender, place, summary, fifthLineWR, sixthLineWR , 6000);
} else {
show_display("< MSG Rx >", "From --> " + Sender, "", receivedMessage , 3000);
saveNewMessage("APRS", Sender, iGate, receivedMessage);
}
} else {
show_display("< MSG >", "From --> " + Sender, "", receivedMessage , 3000);
saveNewMessage("APRS", Sender, iGate, receivedMessage);
}
}
} else if (packetReceived.indexOf(":!") > 10 || packetReceived.indexOf(":=") > 10 ) { // packetReceived has APRS - GPS info
lastHeardTracker = Sender; // eliminar?
if (packetReceived.indexOf(":!/") > 10) { // encoded GPS
decodeEncodedGPS(packetReceived, Sender);
} else {
getReceivedGPS(packetReceived, Sender); // not encoded GPS }
}
}
}
}
}
// cppcheck-suppress unusedFunction
void setup() {
Serial.begin(115200);
#ifdef TTGO_T_Beam_V1_0
Wire.begin(SDA, SCL);
if (!powerManagement.begin(Wire)) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "AXP192", "init done!");
} else {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "AXP192", "init failed!");
}
powerManagement.activateLoRa();
powerManagement.activateOLED();
powerManagement.activateGPS();
powerManagement.activateMeasurement();
#endif
delay(500);
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "RichonGuzman -> CD2RXU --> LoRa APRS Tracker/Station");
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "Version: " VERSION);
setup_display();
show_display(" LoRa APRS", "", " Richonguzman", " -- CD2RXU --", "", " " VERSION, 4000);
validateConfigFile();
loadNumMessages();
setup_gps();
setup_lora();
WiFi.mode(WIFI_OFF);
btStop();
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "WiFi and BT controller stopped");
esp_bt_controller_disable();
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "BT controller disabled");
userButton.attachClick(ButtonSinglePress);
userButton.attachLongPressStart(ButtonLongPress);
userButton.attachDoubleClick(ButtonDoublePress);
#if defined(TTGO_T_Beam_V1_0)
if (setCpuFrequencyMhz(80)) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency set to 80MHz");
} else {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency unchanged");
}
#endif
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "Smart Beacon is: %s", getSmartBeaconState());
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "Setup Done!");
delay(500);
}
// cppcheck-suppress unusedFunction
void loop() {
uint32_t lastDisplayTime = millis() - displayTime;
if (displayEcoMode) {
if (menuDisplay == 0 && lastDisplayTime >= Config.displayTimeout*1000) {
display_toggle(false);
displayState = false;
}
}
currentBeacon = &Config.beacons[myBeaconsIndex];
userButton.tick();
while (neo6m_gps.available() > 0) {
gps.encode(neo6m_gps.read());
}
bool gps_time_update = gps.time.isUpdated();
bool gps_loc_update = gps.location.isUpdated();
String loraPacket = "";
int packetSize = LoRa.parsePacket(); // Listening for LoRa Packets
if (packetSize) {
while (LoRa.available()) {
int inChar = LoRa.read();
loraPacket += (char)inChar;
}
checkReceivedMessage(loraPacket);
}
if (millis() - lastDeleteListenedTracker > Config.listeningTrackerTime*60*1000) {
deleteListenedTrackersbyTime();
}
/*if (gps_loc_update != gps_loc_update_valid) {
gps_loc_update_valid = gps_loc_update;
if (gps_loc_update) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Loop", "GPS fix state went to VALID");
} else {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Loop", "GPS fix state went to INVALID");
}
}*/
static double currentHeading = 0;
static double previousHeading = 0;
if (gps.time.isValid()) {
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(), gps.date.day(), gps.date.month(), gps.date.year());
}
static double lastTxLat = 0.0;
static double lastTxLng = 0.0;
static double lastTxDistance = 0.0;
static uint32_t txInterval = 60000L;
static uint32_t lastTxTime = millis();
static bool sendStandingUpdate = false;
int currentSpeed = (int)gps.speed.kmph();
static bool BatteryIsConnected = false;
static String batteryVoltage = "";
static String batteryChargeCurrent = "";
#ifdef TTGO_T_Beam_V1_0
static unsigned int rate_limit_check_battery = 0;
if (!(rate_limit_check_battery++ % 60))
BatteryIsConnected = powerManagement.isBatteryConnect();
if (BatteryIsConnected) {
batteryVoltage = String(powerManagement.getBatteryVoltage(), 2);
batteryChargeCurrent = String(powerManagement.getBatteryChargeDischargeCurrent(), 0);
}
#endif
if (powerManagement.isChargeing()) {
powerManagement.enableChgLed();
} else {
powerManagement.disableChgLed();
}
if (!send_update && gps_loc_update && currentBeacon->smartBeaconState) {
uint32_t lastTx = millis() - lastTxTime;
currentHeading = gps.course.deg();
lastTxDistance = TinyGPSPlus::distanceBetween(gps.location.lat(), gps.location.lng(), lastTxLat, lastTxLng);
if (lastTx >= txInterval) {
if (lastTxDistance > currentBeacon->minTxDist) {
send_update = true;
sendStandingUpdate = false;
}
}
if (!send_update) {
int TurnMinAngle;
double headingDelta = abs(previousHeading - currentHeading);
if (lastTx > currentBeacon->minDeltaBeacon * 1000) {
if (currentSpeed == 0) {
TurnMinAngle = currentBeacon->turnMinDeg + (currentBeacon->turnSlope/(currentSpeed+1));
} else {
TurnMinAngle = currentBeacon->turnMinDeg + (currentBeacon->turnSlope/currentSpeed);
}
if (headingDelta > TurnMinAngle && lastTxDistance > currentBeacon->minTxDist) {
send_update = true;
sendStandingUpdate = false;
}
}
}
if (!send_update && lastTx >= Config.standingUpdateTime*60*1000) {
send_update = true;
sendStandingUpdate = true;
}
}
if (!currentBeacon->smartBeaconState) {
uint32_t lastTx = millis() - lastTxTime;
if (lastTx >= Config.nonSmartBeaconRate*60*1000) {
send_update = true;
}
}
if (send_update && gps_loc_update) {
APRSMessage msg;
msg.setSource(currentBeacon->callsign);
msg.setDestination(Config.destination);
msg.setPath(Config.path);
float Tlat, Tlon;
float Tspeed=0, Tcourse=0;
Tlat = gps.location.lat();
Tlon = gps.location.lng();
Tcourse = gps.course.deg();
Tspeed = gps.speed.knots();
uint32_t aprs_lat, aprs_lon;
aprs_lat = 900000000 - Tlat * 10000000;
aprs_lat = aprs_lat / 26 - aprs_lat / 2710 + aprs_lat / 15384615;
aprs_lon = 900000000 + Tlon * 10000000 / 2;
aprs_lon = aprs_lon / 26 - aprs_lon / 2710 + aprs_lon / 15384615;
String Ns, Ew, helper;
if(Tlat < 0) { Ns = "S"; } else { Ns = "N"; }
if(Tlat < 0) { Tlat= -Tlat; }
if(Tlon < 0) { Ew = "W"; } else { Ew = "E"; }
if(Tlon < 0) { Tlon= -Tlon; }
String infoField = "!";
infoField += Config.overlay;
char helper_base91[] = {"0000\0"};
int i;
ax25_base91enc(helper_base91, 4, aprs_lat);
for (i=0; i<4; i++) {
infoField += helper_base91[i];
}
ax25_base91enc(helper_base91, 4, aprs_lon);
for (i=0; i<4; i++) {
infoField += helper_base91[i];
}
infoField += currentBeacon->symbol;
if (Config.sendAltitude) { // Send Altitude or... (APRS calculates Speed also)
int Alt1, Alt2;
int Talt;
Talt = gps.altitude.feet();
if(Talt>0){
double ALT=log(Talt)/log(1.002);
Alt1= int(ALT/91);
Alt2=(int)ALT%91;
}else{
Alt1=0;
Alt2=0;
}
if (sendStandingUpdate) {
infoField += " ";
} else {
infoField +=char(Alt1+33);
}
infoField +=char(Alt2+33);
infoField +=char(0x30+33);
} else { // ... just send Course and Speed
ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
if (sendStandingUpdate) {
infoField += " ";
} else {
infoField += helper_base91[0];
}
ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
infoField += helper_base91[0];
infoField += "\x47";
}
if (currentBeacon->comment != "") {
infoField += currentBeacon->comment;
}
msg.getBody()->setData(infoField);
String data = msg.encode();
logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Loop", "%s", data.c_str());
show_display("<<< TX >>>", "", data);
sendNewLoraPacket(data);
if (currentBeacon->smartBeaconState) {
lastTxLat = gps.location.lat();
lastTxLng = gps.location.lng();
previousHeading = currentHeading;
lastTxDistance = 0.0;
}
lastTxTime = millis();
send_update = false;
if (Config.defaultStatusAfterBoot && statusAfterBootState) {
startingStatus();
}
}
if (gps_time_update) {
switch (menuDisplay) { // Graphic Menu is in here!!!!
case 1:
show_display("__MENU_1__", "", "1P -> Read Msg (" + String(numAPRSMessages) + ")", "LP -> Delete Msg", "2P -> Menu 2");
break;
case 2:
show_display("__MENU_2__", "", "1P -> Weather Report", "LP -> Listen Trackers", "2P -> Menu 3");
break;
case 3:
show_display("__MENU_3__", "", "1P -> Nothing Yet", "LP -> Display EcoMode", "2P -> (Back) Tracking");
break;
case 10: // Display Received/Saved APRS Messages
{
String msgSender = loadedAPRSMessages[messagesIterator].substring(0, loadedAPRSMessages[messagesIterator].indexOf(","));
String restOfMessage = loadedAPRSMessages[messagesIterator].substring(loadedAPRSMessages[messagesIterator].indexOf(",")+1);
String msgGate = restOfMessage.substring(0,restOfMessage.indexOf(","));
String msgText = restOfMessage.substring(restOfMessage.indexOf(",")+1);
show_display("MSG_APRS>", msgSender + "-->" + msgGate, msgText, "", "", " Next>");
}
break;
case 20: // Display Heared Tracker/Stations
show_display("LISTENING>", String(firstNearTracker.substring(0,firstNearTracker.indexOf(","))), String(secondNearTracker.substring(0,secondNearTracker.indexOf(","))), String(thirdNearTracker.substring(0,thirdNearTracker.indexOf(","))), String(fourthNearTracker.substring(0,fourthNearTracker.indexOf(","))), "<Back");
break;
case 0: ///////////// MAIN MENU //////////////
String hdopState, firstRowMainMenu, secondRowMainMenu, thirdRowMainMenu, fourthRowMainMenu, fifthRowMainMenu, sixthRowMainMenu;;
firstRowMainMenu = currentBeacon->callsign;
secondRowMainMenu = createDateString(now()) + " " + createTimeString(now());
if (gps.hdop.hdop() > 5) {
hdopState = "X";
} else if (gps.hdop.hdop() > 2 && gps.hdop.hdop() < 5) {
hdopState = "-";
} else if (gps.hdop.hdop() <= 2) {
hdopState = "+";
}
thirdRowMainMenu = String(gps.location.lat(), 4) + " " + String(gps.location.lng(), 4);
for(int i = thirdRowMainMenu.length(); i < 18; i++) {
thirdRowMainMenu += " ";
}
if (gps.satellites.value() > 9) {
thirdRowMainMenu += String(gps.satellites.value()) + hdopState;
} else {
thirdRowMainMenu += " " + String(gps.satellites.value()) + hdopState;
}
String fourthRowAlt = String(gps.altitude.meters(),0);
fourthRowAlt.trim();
for (int a=fourthRowAlt.length();a<4;a++) {
fourthRowAlt = "0" + fourthRowAlt;
}
String fourthRowSpeed = String(gps.speed.kmph(),0);
fourthRowSpeed.trim();
for (int b=fourthRowSpeed.length(); b<3;b++) {
fourthRowSpeed = " " + fourthRowSpeed;
}
String fourthRowCourse = String(gps.course.deg(),0);
if (fourthRowSpeed == " 0") {
fourthRowCourse = "---";
} else {
fourthRowCourse.trim();
for(int c=fourthRowCourse.length();c<3;c++) {
fourthRowCourse = "0" + fourthRowCourse;
}
}
fourthRowMainMenu = "A=" + fourthRowAlt + "m " + fourthRowSpeed + "km/h " + fourthRowCourse;
if (numAPRSMessages > 0){
fourthRowMainMenu = "*** MESSAGES: " + String(numAPRSMessages) + " ***";
}
fifthRowMainMenu = "LAST Rx = " + lastHeardTracker;
if (BatteryIsConnected) {
if (batteryChargeCurrent.toInt() == 0) {
sixthRowMainMenu = "Battery Charged " + String(batteryVoltage) + "V";
} else if (batteryChargeCurrent.toInt() > 0) {
sixthRowMainMenu = "Bat: " + String(batteryVoltage) + "V (charging)";
} else {
sixthRowMainMenu = "Battery " + String(batteryVoltage) + "V " + String(batteryChargeCurrent) + "mA";
}
} else {
sixthRowMainMenu = "No Battery Connected." ;
}
show_display(String(firstRowMainMenu),
String(secondRowMainMenu),
String(thirdRowMainMenu),
String(fourthRowMainMenu),
String(fifthRowMainMenu),
String(sixthRowMainMenu));
break;
}
if (currentBeacon->smartBeaconState) {
if (currentSpeed < currentBeacon->slowSpeed) {
txInterval = currentBeacon->slowRate * 1000;
} else if (currentSpeed > currentBeacon->fastSpeed) {
txInterval = currentBeacon->fastRate * 1000;
} else {
txInterval = min(currentBeacon->slowRate, currentBeacon->fastSpeed * currentBeacon->fastRate / currentSpeed) * 1000;
}
}
}
if ((millis() > 5000 && gps.charsProcessed() < 10)) {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "GPS",
"No GPS frames detected! Try to reset the GPS Chip with this "
"firmware: https://github.com/lora-aprs/TTGO-T-Beam_GPS-reset");
show_display("No GPS frames detected!", "Try to reset the GPS Chip", "https://github.com/lora-aprs/TTGO-T-Beam_GPS-reset", 2000);
}
}
/// FUNCTIONS ///
void validateConfigFile() {
if (currentBeacon->callsign == "NOCALL-7") {
logger.log(logging::LoggerLevel::LOGGER_LEVEL_ERROR, "Config", "Change your settings in 'data/tracker_config.json' and upload it via 'Upload File System image'");
show_display("ERROR", "Change your settings", "'tracker_config.json'", "upload it via --> ", "'Upload File System image'");
while (true) {}
}
}
char *ax25_base91enc(char *s, uint8_t n, uint32_t v) {
/* Creates a Base-91 representation of the value in v in the string */
/* pointed to by s, n-characters long. String length should be n+1. */
for(s += n, *s = '\0'; n; n--) {
*(--s) = v % 91 + 33;
v /= 91;
}
return(s);
}
String createDateString(time_t t) {
return String(padding(year(t), 4) + "-" + padding(month(t), 2) + "-" + padding(day(t), 2));
}
String createTimeString(time_t t) {
return String(padding(hour(t), 2) + ":" + padding(minute(t), 2) + ":" + padding(second(t), 2));
}
String getSmartBeaconState() {
if (currentBeacon->smartBeaconState) {
return "On";
}
return "Off";
}
String padding(unsigned int number, unsigned int width) {
String result;
String num(number);
if (num.length() > width) {
width = num.length();
}
for (unsigned int i = 0; i < width - num.length(); i++) {
result.concat('0');
}
result.concat(num);
return result;
}