#include #include #include #include #include #include #include #include #include #include "pins_config.h" #include "igate_config.h" #include "display.h" #define VERSION "2023.05.23" WiFiClient espClient; String ConfigurationFilePath = "/igate_conf.json"; Configuration Config(ConfigurationFilePath); uint32_t lastTxTime = 0; static bool beacon_update = true; unsigned long previousWiFiMillis = 0; static uint32_t lastRxTxTime = millis(); static int myWiFiAPIndex = 0; int myWiFiAPSize = Config.wifiAPs.size(); WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex]; std::vector lastHeardStation; std::vector lastHeardStation2; static uint32_t startUpTime = millis(); String firstLine, secondLine, thirdLine, fourthLine, iGateLatitude, iGateLongitude; void setup_wifi() { int status = WL_IDLE_STATUS; Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.print("' "); show_display(" ", "Connecting to Wifi:", currentWiFi->ssid + " ...", 0); WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); unsigned long start = millis(); WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); if ((millis() - start) > 15000){ if(myWiFiAPIndex >= (myWiFiAPSize-1)) { myWiFiAPIndex = 0; } else { myWiFiAPIndex++; } currentWiFi = &Config.wifiAPs[myWiFiAPIndex]; start = millis(); Serial.print("\nConnect to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ..."); show_display(" ", "Connect to Wifi:", currentWiFi->ssid + " ...", 0); WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); } } Serial.print("Connected as "); Serial.println(WiFi.localIP()); } void setup_lora() { SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ); if (!LoRa.begin(Config.loramodule.frequency)) { Serial.println("Starting LoRa failed!"); while (true) { } } LoRa.setSpreadingFactor(Config.loramodule.spreading_factor); LoRa.setSignalBandwidth(Config.loramodule.signal_bandwidth); LoRa.setCodingRate4(Config.loramodule.coding_rate4); LoRa.enableCrc(); LoRa.setTxPower(Config.loramodule.power); Serial.println("LoRa init done!\n"); } void APRS_IS_connect(){ int count = 0; String aprsauth; Serial.println("Connecting to APRS-IS ..."); while (!espClient.connect(Config.aprs_is.server.c_str(), Config.aprs_is.port) && count < 20) { Serial.println("Didn't connect with server..."); delay(1000); espClient.stop(); espClient.flush(); Serial.println("Run client.stop"); Serial.println("Trying to connect with Server: " + String(Config.aprs_is.server) + " AprsServerPort: " + String(Config.aprs_is.port)); count++; Serial.println("Try: " + String(count)); } if (count == 20) { Serial.println("Tried: " + String(count) + " FAILED!"); } else { Serial.println("Connected with Server: '" + String(Config.aprs_is.server) + "' (port: " + String(Config.aprs_is.port)+ ")"); aprsauth = "user " + Config.callsign + " pass " + Config.aprs_is.passcode + " vers " + Config.aprs_is.software_name + " " + Config.aprs_is.software_version + " filter t/m/" + Config.callsign + "/" + (String)Config.aprs_is.reporting_distance + "\n\r"; espClient.write(aprsauth.c_str()); delay(200); } } String createAPRSPacket(String unprocessedPacket) { String callsign_and_path_tracker, payload_tracker, processedPacket; int dotsPosition = unprocessedPacket.indexOf(':'); callsign_and_path_tracker = unprocessedPacket.substring(3, dotsPosition); payload_tracker = unprocessedPacket.substring(dotsPosition); processedPacket = callsign_and_path_tracker + ",qAO," + Config.callsign + payload_tracker + "\n"; return processedPacket; } bool checkValidHeardStation(String station) { bool validStation = false; for (int i=0; i Listened Station"); } } if (!validStation) { Serial.println(" ---> Station not Heard for last 30 min (Not Tx)\n"); } return validStation; } void deleteNotHeardStation() { uint32_t minReportingTime = 30*60*1000; // 30 minutes // from .json and CONFIGURATION????? for (int i=0; i LoRa Packet Tx : "); Serial.println(newPacket); } String processQueryAnswer(String query, String station, String queryOrigin) { String processedQuery, queryAnswer; if (query.indexOf("?APRS?") == 0 || query.indexOf("?aprs?") == 0 || query.indexOf("?Aprs?") == 0) { processedQuery = "?APRSV ?APRSP ?APRSL ?APRSH ?WHERE callsign"; } else if (query.indexOf("?APRSV") == 0 || query.indexOf("?aprsv") == 0 || query.indexOf("?Aprsv") == 0) { processedQuery = Config.aprs_is.software_name + " " + Config.aprs_is.software_version; } else if (query.indexOf("?APRSP") == 0 || query.indexOf("?aprsp") == 0 || query.indexOf("?Aprsp") == 0) { processedQuery = "iGate QTH: " + String(currentWiFi->latitude) + " " + String(currentWiFi->longitude); } else if (query.indexOf("?APRSL") == 0 || query.indexOf("?aprsl") == 0 || query.indexOf("?Aprsl") == 0) { for (int i=0; iAPLG01,TCPIP,qAC::" + station + ":" + processedQuery + "\n"; } else if (queryOrigin == "LoRa") { queryAnswer = Config.callsign + ">APLG01,RFONLY::" + station + ":" + processedQuery; } return queryAnswer; } void checkReceivedPacket(String packet) { bool queryMessage = false; String aprsPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, receivedMessage, queryAnswer; Serial.print("Received Lora Packet : " + String(packet)); if ((packet.substring(0, 3) == "\x3c\xff\x01") && (packet.indexOf("TCPIP") == -1) && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) { Serial.print(" ---> APRS LoRa Packet!"); Sender = packet.substring(3,packet.indexOf(">")); if (Sender != Config.callsign) { // avoid listening yourself by digirepeating if (packet.indexOf("::") > 10) { // its a Message! AddresseeAndMessage = packet.substring(packet.indexOf("::")+2); Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":")); Addressee.trim(); if (Addressee == Config.callsign) { // its for me! if (AddresseeAndMessage.indexOf("{")>0) { // ack? ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); ackMessage.trim(); delay(4000); Serial.println(ackMessage); for(int i = Sender.length(); i < 9; i++) { Sender += ' '; } sendNewLoraPacket("APRS", Config.callsign + ">APLG01,RFONLY::" + Sender + ":" + ackMessage); receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); } else { receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); } if (receivedMessage.indexOf("?") == 0) { queryMessage = true; queryAnswer = processQueryAnswer(receivedMessage, Sender, "LoRa"); delay(2000); if (!Config.display.always_on) { display_toggle(true); } lastRxTxTime = millis(); sendNewLoraPacket("APRS", queryAnswer); show_display("LoRa iGate: " + Config.callsign, secondLine, "Callsign = " + Sender, "Type --> QUERY", 1000); Serial.println(queryAnswer); } } } if (!queryMessage) { aprsPacket = createAPRSPacket(packet); if (!Config.display.always_on) { display_toggle(true); } lastRxTxTime = millis(); espClient.write(aprsPacket.c_str()); Serial.println(" ---> Uploaded to APRS-IS"); deleteNotHeardStation(); updateLastHeardStation(Sender); if (aprsPacket.indexOf("::") >= 10) { show_display("LoRa iGate: " + Config.callsign, secondLine, "Callsign = " + Sender, "Type --> MESSAGE", 1000); } else if (aprsPacket.indexOf(":>") >= 10) { show_display("LoRa iGate: " + Config.callsign, secondLine, "Callsign = " + Sender, "Type --> NEW STATUS", 1000); } else { show_display("LoRa iGate: " + Config.callsign, secondLine, "Callsign = " + Sender, "Type --> GPS BEACON", 1000); } } } } else { Serial.println(" ---> Not APRS Packet (Ignore)\n"); } } String processAPRSISPacket(String aprsisMessage) { String firstPart, messagePart, newLoraPacket; aprsisMessage.trim(); firstPart = aprsisMessage.substring(0, aprsisMessage.indexOf(",")); messagePart = aprsisMessage.substring(aprsisMessage.indexOf("::")+2); newLoraPacket = firstPart + ",TCPIP," + Config.callsign + "::" + messagePart; Serial.print("Received from APRS-IS : " + aprsisMessage); return newLoraPacket; } String double2string(double n, int ndec) { String r = ""; int v = n; r += v; r += '.'; int i; for (i=0;ilatitude); iGateLongitude = create_lng_aprs(currentWiFi->longitude); } void loop() { String wifiState, aprsisState; firstLine = "LoRa iGate: " + Config.callsign; secondLine = " "; thirdLine = " "; fourthLine = " "; unsigned long currentWiFiMillis = millis(); if ((WiFi.status() != WL_CONNECTED) && (currentWiFiMillis - previousWiFiMillis >= 30000)) { Serial.print(millis()); Serial.println("Reconnecting to WiFi..."); WiFi.disconnect(); WiFi.reconnect(); previousWiFiMillis = currentWiFiMillis; } if (!espClient.connected()) { APRS_IS_connect(); } if (WiFi.status() == WL_CONNECTED) { wifiState = "OK"; } else { wifiState = "--"; if (!Config.display.always_on) { display_toggle(true); } lastRxTxTime = millis(); } if (espClient.connected()) { aprsisState = "OK"; } else { aprsisState = "--"; if (!Config.display.always_on) { display_toggle(true); } lastRxTxTime = millis(); } secondLine = "WiFi: " + wifiState + "/ APRS-IS: " + aprsisState; show_display(firstLine, secondLine, thirdLine, fourthLine, 0); while (espClient.connected()) { uint32_t lastRxTx = millis() - lastRxTxTime; if (!Config.display.always_on) { if (lastRxTx >= Config.display.timeout*1000) { display_toggle(false); } } thirdLine = " "; fourthLine = " "; show_display(firstLine, secondLine, thirdLine, fourthLine, 0); uint32_t lastTx = millis() - lastTxTime; if (lastTx >= Config.beacon_interval*60*1000) { beacon_update = true; } if (beacon_update) { display_toggle(true); Serial.println("---- Sending iGate Beacon ----"); String iGateBeaconPacket = Config.callsign + ">APLG01,qAC:=" + iGateLatitude + "L" + iGateLongitude + "&" + Config.comment + "\n"; //Serial.println(iGateBeaconPacket); espClient.write(iGateBeaconPacket.c_str()); lastTxTime = millis(); lastRxTxTime = millis(); show_display(firstLine, secondLine, thirdLine, "SENDING iGate BEACON", 1000); beacon_update = false; } String loraPacket = ""; int packetSize = LoRa.parsePacket(); if (packetSize) { while (LoRa.available()) { int inChar = LoRa.read(); loraPacket += (char)inChar; } checkReceivedPacket(loraPacket); } if (espClient.available()) { String aprsisData, aprsisPacket, newLoraPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, ackPacket, receivedMessage, queryAnswer; bool validHeardStation = false; aprsisData = espClient.readStringUntil('\r'); // or '\n' aprsisPacket.concat(aprsisData); if (!aprsisPacket.startsWith("#")){ if (aprsisPacket.indexOf("::")>0) { Sender = aprsisPacket.substring(0,aprsisPacket.indexOf(">")); AddresseeAndMessage = aprsisPacket.substring(aprsisPacket.indexOf("::")+2); Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); Addressee.trim(); if (Addressee == Config.callsign) { // its for me! if (AddresseeAndMessage.indexOf("{")>0) { // ack? ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); ackMessage.trim(); delay(4000); Serial.println(ackMessage); for(int i = Sender.length(); i < 9; i++) { Sender += ' '; } ackPacket = Config.callsign + ">APLG01,TCPIP,qAC::" + Sender + ":" + ackMessage + "\n"; espClient.write(ackPacket.c_str()); receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); } else { receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); } if (receivedMessage.indexOf("?") == 0) { Serial.println("Received Query APRS-IS : " + aprsisPacket); queryAnswer = processQueryAnswer(receivedMessage, Sender, "APRSIS"); Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n"))); if (!Config.display.always_on) { display_toggle(true); } lastRxTxTime = millis(); delay(500); espClient.write(queryAnswer.c_str()); show_display("LoRa iGate: " + Config.callsign, secondLine, "Callsign = " + Sender, "Type --> QUERY", 1000); } } else { newLoraPacket = processAPRSISPacket(aprsisPacket); deleteNotHeardStation(); validHeardStation = checkValidHeardStation(Addressee); if (validHeardStation) { sendNewLoraPacket("APRS", newLoraPacket); display_toggle(true); lastRxTxTime = millis(); show_display(firstLine, secondLine, Sender + " -> " + Addressee, receivedMessage, 2000); } } } } } } }