#include #include #include "APRSPacketLib.h" #include "station_utils.h" #include "configuration.h" #include "power_utils.h" #include "lora_utils.h" #include "bme_utils.h" #include "display.h" #include "logger.h" extern Configuration Config; extern Beacon *currentBeacon; extern logging::Logger logger; extern TinyGPSPlus gps; extern uint8_t myBeaconsIndex; extern uint8_t loraIndex; extern uint32_t lastTx; extern uint32_t lastTxTime; extern bool sendUpdate; extern uint32_t txInterval; extern double currentHeading; extern double previousHeading; extern double lastTxLat; extern double lastTxLng; extern double lastTxDistance; extern bool miceActive; extern bool smartBeaconValue; extern uint8_t winlinkStatus; extern bool winlinkCommentState; extern int wxModuleType; bool sendStandingUpdate = false; uint8_t updateCounter = Config.sendCommentAfterXBeacons; bool wxRequestStatus = false; uint32_t wxRequestTime = 0; uint32_t lastTelemetryTx = 0; uint32_t telemetryTx = millis(); uint32_t lastDeleteListenedTracker; struct nearTracker { String callsign; float distance; int course; uint32_t lastTime; }; nearTracker nearTrackers[4]; namespace STATION_Utils { void nearTrackerInit() { for (int i = 0; i < 4; i++) { nearTrackers[i].callsign = ""; nearTrackers[i].distance = 0.0; nearTrackers[i].course = 0; nearTrackers[i].lastTime = 0; } } const String getNearTracker(uint8_t position) { if (nearTrackers[position].callsign == "") { return ""; } else { return nearTrackers[position].callsign + "> " + String(nearTrackers[position].distance,2) + "km " + String(nearTrackers[position].course); } } void deleteListenedTrackersbyTime() { for (int a = 0; a < 4; a++) { // clean nearTrackers[] after time if (nearTrackers[a].callsign != "" && (millis() - nearTrackers[a].lastTime > Config.rememberStationTime * 60 * 1000)) { nearTrackers[a].callsign = ""; nearTrackers[a].distance = 0.0; nearTrackers[a].course = 0; nearTrackers[a].lastTime = 0; } } for (int b = 0; b < 4 - 1; b++) { for (int c = 0; c < 4 - b - 1; c++) { if (nearTrackers[c].callsign == "") { // get "" nearTrackers[] at the end nearTracker temp = nearTrackers[c]; nearTrackers[c] = nearTrackers[c + 1]; nearTrackers[c + 1] = temp; } } } lastDeleteListenedTracker = millis(); } void checkListenedTrackersByTimeAndDelete() { if (millis() - lastDeleteListenedTracker > Config.rememberStationTime * 60 * 1000) { deleteListenedTrackersbyTime(); } } void orderListenedTrackersByDistance(const String& callsign, float distance, float course) { bool shouldSortbyDistance = false; bool callsignInNearTrackers = false; for (int a = 0; a < 4; a++) { // check if callsign is in nearTrackers[] if (nearTrackers[a].callsign == callsign) { callsignInNearTrackers = true; nearTrackers[a].lastTime = millis(); // update listened millis() if (nearTrackers[a].distance != distance) { // update distance if needed nearTrackers[a].distance = distance; shouldSortbyDistance = true; } break; } } if (!callsignInNearTrackers) { // callsign not in nearTrackers[] for (int b = 0; b < 4; b++) { // if nearTrackers[] is available if (nearTrackers[b].callsign == "") { shouldSortbyDistance = true; nearTrackers[b].callsign = callsign; nearTrackers[b].distance = distance; nearTrackers[b].course = int(course); nearTrackers[b].lastTime = millis(); break; } } if (!shouldSortbyDistance) { // if no more nearTrackers[] available , it compares distances to move and replace for (int c = 0; c < 4; c++) { if (nearTrackers[c].distance > distance) { for (int d = 3; d > c; d--) { nearTrackers[d] = nearTrackers[d - 1]; } nearTrackers[c].callsign = callsign; nearTrackers[c].distance = distance; nearTrackers[c].course = int(course); nearTrackers[c].lastTime = millis(); break; } } } } if (shouldSortbyDistance) { // sorts by distance (only nearTrackers[] that are not "") for (int f = 0; f < 4 - 1; f++) { for (int g = 0; g < 4 - f - 1; g++) { if (nearTrackers[g].callsign != "" && nearTrackers[g + 1].callsign != "") { if (nearTrackers[g].distance > nearTrackers[g + 1].distance) { nearTracker temp = nearTrackers[g]; nearTrackers[g] = nearTrackers[g + 1]; nearTrackers[g + 1] = temp; } } } } } } void checkSmartBeaconInterval(int speed) { if (smartBeaconValue) { if (speed < currentBeacon->slowSpeed) { txInterval = currentBeacon->slowRate * 1000; } else if (speed > currentBeacon->fastSpeed) { txInterval = currentBeacon->fastRate * 1000; } else { txInterval = min(currentBeacon->slowRate, currentBeacon->fastSpeed * currentBeacon->fastRate / speed) * 1000; } } } void checkStandingUpdateTime() { if (!sendUpdate && lastTx >= Config.standingUpdateTime * 60 * 1000) { sendUpdate = true; sendStandingUpdate = true; } } void checkSmartBeaconValue() { if (wxRequestStatus && (millis() - wxRequestTime) > 20000) { wxRequestStatus = false; } if(winlinkStatus == 0 && !wxRequestStatus) { smartBeaconValue = currentBeacon->smartBeaconState; } else { smartBeaconValue = false; } } void checkSmartBeaconState() { if (!smartBeaconValue) { uint32_t lastTxSmartBeacon = millis() - lastTxTime; if (lastTxSmartBeacon >= Config.nonSmartBeaconRate * 60 * 1000) { sendUpdate = true; } } } void sendBeacon(uint8_t type) { String packet; if (Config.bme.sendTelemetry && type == 1) { // WX packet = APRSPacketLib::generateGPSBeaconPacket(currentBeacon->callsign, "APLRT1", Config.path, "/", APRSPacketLib::encodeGPS(gps.location.lat(),gps.location.lng(), gps.course.deg(), 0.0, currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, "Wx")); if (wxModuleType != 0) { packet += BME_Utils::readDataSensor(0); } else { packet += ".../...g...t...r...p...P...h..b....."; } } else { String path = Config.path; if (gps.speed.kmph() > 200 || gps.altitude.meters() > 9000) { // avoid plane speed and altitude path = ""; } if (miceActive) { packet = APRSPacketLib::generateMiceGPSBeacon(currentBeacon->micE, currentBeacon->callsign, currentBeacon->symbol, currentBeacon->overlay, path, gps.location.lat(), gps.location.lng(), gps.course.deg(), gps.speed.knots(), gps.altitude.meters()); } else { packet = APRSPacketLib::generateGPSBeaconPacket(currentBeacon->callsign, "APLRT1", path, currentBeacon->overlay, APRSPacketLib::encodeGPS(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, "GPS")); } } String comment; int sendCommentAfterXBeacons; if (winlinkCommentState) { comment = " winlink"; sendCommentAfterXBeacons = 1; } else { comment = currentBeacon->comment; sendCommentAfterXBeacons = Config.sendCommentAfterXBeacons; } if (Config.sendBatteryInfo) { String batteryVoltage = POWER_Utils::getBatteryInfoVoltage(); String batteryChargeCurrent = POWER_Utils::getBatteryInfoCurrent(); #ifdef HAS_AXP192 comment += " Bat="; comment += batteryVoltage; comment += "V ("; comment += batteryChargeCurrent; comment += "mA)"; #endif #ifdef HAS_AXP2101 comment += " Bat="; comment += String(batteryVoltage.toFloat()/1000,2); comment += "V ("; comment += batteryChargeCurrent; comment += "%)"; #endif #if defined(HELTEC_V3_GPS) || defined(HELTEC_WIRELESS_TRACKER) || defined(TTGO_T_DECK_GPS) comment += " Bat="; comment += String(batteryVoltage.toFloat(),2); comment += "V"; #endif } if (comment != "") { updateCounter++; if (updateCounter >= sendCommentAfterXBeacons) { packet += comment; updateCounter = 0; } } #ifdef HAS_TFT cleanTFT(); #endif show_display("<<< TX >>>", "", packet,100); LoRa_Utils::sendNewPacket(packet); if (smartBeaconValue) { lastTxLat = gps.location.lat(); lastTxLng = gps.location.lng(); previousHeading = currentHeading; lastTxDistance = 0.0; } lastTxTime = millis(); sendUpdate = false; } void checkTelemetryTx() { if (Config.bme.active && Config.bme.sendTelemetry && sendStandingUpdate) { lastTx = millis() - lastTxTime; telemetryTx = millis() - lastTelemetryTx; if ((lastTelemetryTx == 0 || telemetryTx > 10 * 60 * 1000) && lastTx > 10 * 1000) { sendBeacon(1); lastTelemetryTx = millis(); } } } void saveIndex(uint8_t type, uint8_t index) { String filePath; if (type == 0) { filePath = "/callsignIndex.txt"; } else { filePath = "/freqIndex.txt"; } File fileIndex = SPIFFS.open(filePath, "w"); if(!fileIndex) { return; } String dataToSave = String(index); if (fileIndex.println(dataToSave)) { if (type == 0) { logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "New Callsign Index saved to SPIFFS"); } else { logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "New Frequency Index saved to SPIFFS"); } } fileIndex.close(); } void loadIndex(uint8_t type) { String filePath; if (type == 0) { filePath = "/callsignIndex.txt"; } else { filePath = "/freqIndex.txt"; } File fileIndex = SPIFFS.open(filePath); if(!fileIndex) { return; } else { while (fileIndex.available()) { String firstLine = fileIndex.readStringUntil('\n'); if (type == 0) { myBeaconsIndex = firstLine.toInt(); logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "Callsign Index: %s", firstLine); } else { loraIndex = firstLine.toInt(); logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "LoRa", "LoRa Freq Index: %s", firstLine); } } fileIndex.close(); } } }