diff --git a/Firmware/RTK_Everywhere/AP-Config/index.html b/Firmware/RTK_Everywhere/AP-Config/index.html index 2308b95e0..797aa3c92 100644 --- a/Firmware/RTK_Everywhere/AP-Config/index.html +++ b/Firmware/RTK_Everywhere/AP-Config/index.html @@ -1168,6 +1168,54 @@

+ +
+ + + + + +
+ +
+
+ + + + + +
+
+ +
+ + + + + +
+ +
+ + + + + +
+
+ + @@ -1309,29 +1357,17 @@ - -
- - - - - -
- -
-
- - - - - -
+ +
+ + + + +
diff --git a/Firmware/RTK_Everywhere/AP-Config/src/main.js b/Firmware/RTK_Everywhere/AP-Config/src/main.js index c8e1c761a..24fa64a56 100644 --- a/Firmware/RTK_Everywhere/AP-Config/src/main.js +++ b/Firmware/RTK_Everywhere/AP-Config/src/main.js @@ -103,11 +103,13 @@ function parseIncoming(msg) { show("ppConfig"); show("ethernetConfig"); show("ntpConfig"); - hide("portsConfig"); + show("portsConfig"); + hide("externalPortOptions"); show("logToSDCard"); hide("galileoHasSetting"); hide("tiltConfig"); hide("beeperControl"); + show("useAssistNowCheckbox"); } else if (platformPrefix == "Facet v2") { show("baseConfig"); @@ -115,11 +117,12 @@ function parseIncoming(msg) { hide("ethernetConfig"); hide("ntpConfig"); show("portsConfig"); - hide("noExternalPortOptions"); + show("externalPortOptions"); show("logToSDCard"); hide("galileoHasSetting"); hide("tiltConfig"); hide("beeperControl"); + show("useAssistNowCheckbox"); } else if (platformPrefix == "Facet mosaic") { show("baseConfig"); @@ -127,10 +130,11 @@ function parseIncoming(msg) { hide("ethernetConfig"); hide("ntpConfig"); show("portsConfig"); - hide("noExternalPortOptions"); + show("externalPortOptions"); show("logToSDCard"); hide("tiltConfig"); hide("beeperControl"); + hide("useAssistNowCheckbox"); } else if (platformPrefix == "Torch") { show("baseConfig"); @@ -138,14 +142,14 @@ function parseIncoming(msg) { hide("ethernetConfig"); hide("ntpConfig"); show("portsConfig"); - hide("externalPortOptions"); - show("noExternalPortOptions"); hide("logToSDCard"); hide("constellationSbas"); //Not supported on UM980 + show("useAssistNowCheckbox"); //Does the PPL use MGA? Not sure... + select = ge("dynamicModel"); let newOption = new Option('Survey', '0'); select.add(newOption, undefined); @@ -435,6 +439,7 @@ function parseIncoming(msg) { ge("enableARPLogging").dispatchEvent(new CustomEvent('change')); ge("enableAutoFirmwareUpdate").dispatchEvent(new CustomEvent('change')); ge("enableAutoReset").dispatchEvent(new CustomEvent('change')); + ge("useLocalizedDistribution").dispatchEvent(new CustomEvent('change')); updateECEFList(); updateGeodeticList(); @@ -1384,6 +1389,15 @@ document.addEventListener("DOMContentLoaded", (event) => { } }); + ge("useLocalizedDistribution").addEventListener("change", function () { + if (ge("useLocalizedDistribution").checked) { + show("localizedDistributionTileLevelDropdown"); + } + else { + hide("localizedDistributionTileLevelDropdown"); + } + }); + ge("enableExternalPulse").addEventListener("change", function () { if (ge("enableExternalPulse").checked == true) { show("externalPulseConfigDetails"); diff --git a/Firmware/RTK_Everywhere/MQTT_Client.ino b/Firmware/RTK_Everywhere/MQTT_Client.ino index e2ef52f87..434f193fb 100644 --- a/Firmware/RTK_Everywhere/MQTT_Client.ino +++ b/Firmware/RTK_Everywhere/MQTT_Client.ino @@ -60,19 +60,20 @@ MQTT_Client.ino ------------------------------------------------------------------------------*/ -// TODO -// We should restructure this. -// The hard-coded MQTT_CLIENT_SUBSCRIBE_KEY/SPARTN/ASSIST states are problematic. -// We should have a vector of topics to be subscribed to. -// Code will add/erase topics to/from the vector as needed. -// If the MQTT Client is OFF and it sees that the vector has at least one entry, -// the client starts, connects and subscribes. -// While connected and subscribed, the MQTT Client keeps checking the vector. -// Topics which are subscribed to but are no longer in the vector are unsubscribed. -// If the vector becomes empty, the MQTT Client disconnects and goes back to OFF. -// This would make it a lot easier to: only subscribe to the key topic when needed; -// subscribe to the localized distribution topic. See #150 -// (This is how Michael's hpg does it...) +// How this works: +// There are two vectors of topics: +// mqttSubscribeTopics contains the topics we should be subscribed to +// mqttClientSubscribedTopics contains the topics we are currently subscribed to +// While connected, the mqttClientUpdate state machine compares mqttClientSubscribedTopics to mqttSubscribeTopics +// New topics on mqttSubscribeTopics are subscribed to one at a time (one topic per call of mqttClientUpdate) +// (this will make things easier for cellular - the LARA-R6 can only subscribe to one topic at once) +// Topics no longer on mqttSubscribeTopics are unsubscribed one at a time (one topic per call of mqttClientUpdate) +// (ditto) +// Initially we subscribe to the key distribution topic and the continental correction topic (if available) +// If enabled, we also subscribe to the AssistNow MGA topic +// If localized distribution is enabled and we have a 3D fix, we subscribe to the dict topic +// When the dict is received, we subscribe to the nearest localized topic and unsubscribe from the continental topic +// When the AssistNow MGA data arrives, we unsubscribe and subscribe to AssistNow updates #ifdef COMPILE_MQTT_CLIENT @@ -94,9 +95,6 @@ enum MQTTClientState MQTT_CLIENT_ON, // WIFI_STATE_START state MQTT_CLIENT_NETWORK_STARTED, // Connecting to WiFi access point or Ethernet MQTT_CLIENT_CONNECTING_2_SERVER, // Connecting to the MQTT server - MQTT_CLIENT_SUBSCRIBE_KEY, // Subscribe to the MQTT_TOPIC_KEY - MQTT_CLIENT_SUBSCRIBE_SPARTN, // Subscribe to the MQTT_TOPIC_SPARTN - MQTT_CLIENT_SUBSCRIBE_ASSIST, // Subscribe to the MQTT_TOPIC_ASSISTNOW MQTT_CLIENT_SERVICES_CONNECTED, // Connected to the MQTT services // Insert new states here MQTT_CLIENT_STATE_MAX // Last entry in the state list @@ -107,9 +105,6 @@ const char *const mqttClientStateName[] = { "MQTT_CLIENT_ON", "MQTT_CLIENT_NETWORK_STARTED", "MQTT_CLIENT_CONNECTING_2_SERVER", - "MQTT_CLIENT_SUBSCRIBE_KEY", - "MQTT_CLIENT_SUBSCRIBE_SPARTN", - "MQTT_CLIENT_SUBSCRIBE_ASSIST", "MQTT_CLIENT_SERVICES_CONNECTED", }; @@ -117,13 +112,21 @@ const int mqttClientStateNameEntries = sizeof(mqttClientStateName) / sizeof(mqtt const RtkMode_t mqttClientMode = RTK_MODE_ROVER | RTK_MODE_BASE_SURVEY_IN; -const char MQTT_TOPIC_ASSISTNOW[] = "/pp/ubx/mga"; // AssistNow (MGA) topic +const String MQTT_TOPIC_ASSISTNOW = "/pp/ubx/mga"; // AssistNow (MGA) topic +const String MQTT_TOPIC_ASSISTNOW_UPDATES = "/pp/ubx/mga/updates"; // AssistNow (MGA) topic - updates only // Note: the key and correction topics are now stored in settings - extracted from ZTP +const char localizedPrefix[] = "pp/ip/L"; // The localized distribution topic prefix. Note: starts with "pp", not "/pp" //---------------------------------------- // Locals //---------------------------------------- +std::vector mqttSubscribeTopics; // List of MQTT topics to be subscribed to +std::vector mqttClientSubscribedTopics; // List of topics currently subscribed to + +String localizedDistributionDictTopic = ""; +String localizedDistributionTileTopic = ""; + static MqttClient *mqttClient; static char *mqttClientCertificateBuffer = nullptr; // Buffer for client certificate @@ -225,9 +228,6 @@ void mqttClientPrintStateSummary() break; case MQTT_CLIENT_CONNECTING_2_SERVER: - case MQTT_CLIENT_SUBSCRIBE_KEY: - case MQTT_CLIENT_SUBSCRIBE_SPARTN: - case MQTT_CLIENT_SUBSCRIBE_ASSIST: systemPrint("Connecting"); break; @@ -289,9 +289,11 @@ void mqttClientPrintStatus() // Called when a subscribed message arrives void mqttClientReceiveMessage(int messageSize) { - const uint16_t mqttLimit = 2048; - static uint8_t *mqttData = nullptr; // Allocate memory to hold the MQTT data. Never freed - if (mqttData == nullptr) + // The Level 3 localized distribution dictionary topic can be up to 25KB + // The full AssistNow (MGA) topic can be ~11KB + const uint16_t mqttLimit = 26000; + static uint8_t *mqttData = nullptr; + if (mqttData == nullptr) // Allocate memory to hold the MQTT data. Never freed { if (online.psram == true) mqttData = (uint8_t *)ps_malloc(mqttLimit); @@ -325,8 +327,86 @@ void mqttClientReceiveMessage(int messageSize) if (mqttCount > 0) { + // Check for localizedDistributionDictTopic + if ((localizedDistributionDictTopic.length() > 0) + && (strcmp(topic, localizedDistributionDictTopic.c_str()) == 0)) + { + // We should be using a JSON library to read the nodes. But JSON is + // heavy on RAM and the dict could be 25KB for Level 3. + // Use sscanf instead. + // + // { + // "tile": "L2N5375W00125", + // "nodeprefix": "pp/ip/L2N5375W00125/", + // "nodes": [ + // "N5200W00300", + // "N5200W00200", + // "N5200W00100", + // "N5200E00000", + // "N5200E00100", + // "N5300W00300", + // "N5300W00200", + // "N5300W00100", + // "N5300E00000", + // "N5400W00200", + // "N5400W00100", + // "N5500W00300", + // "N5500W00200" + // ], + // "endpoint": "pp-eu.services.u-blox.com" + // } + char *nodes = strstr((const char *)mqttData, "\"nodes\":["); + if (nodes != nullptr) + { + nodes += strlen("\"nodes\":["); // Point to the first node + float minDist = 99999.0; // Minimum distance to tile center in centidegrees + char *preservedTile; + char *tile = strtok_r(nodes, ",", &preservedTile); + int latitude = int(gnssGetLatitude() * 100.0); // Centidegrees + int longitude = int(gnssGetLongitude() * 100.0); // Centidegrees + while (tile != nullptr) + { + char ns, ew; + int lat, lon; + if (sscanf(tile, "\"%c%d%c%d\"", &ns, &lat, &ew, &lon) == 4) + { + if (ns == 'S') + lat = 0 - lat; + if (ew == 'W') + lon = 0 - lon; + float factorLon = cos(radians(latitude / 100.0)); // Scale lon by the lat + float distScaled = pow(pow(lat - latitude, 2) + pow((lon - longitude) * factorLon, 2), 0.5); // Calculate distance to tile center in centidegrees + if (distScaled < minDist) + { + minDist = distScaled; + tile[12] = 0; // Convert the second quote to NULL for snprintf + char tileTopic[50]; + snprintf(tileTopic, sizeof(tileTopic), "%s", localizedDistributionDictTopic.c_str()); + snprintf(&tileTopic[strlen(localizedPrefix) + 13], sizeof(tileTopic) - (strlen(localizedPrefix) + 13), "%s", tile + 1); // Start after the first quote + localizedDistributionTileTopic = tileTopic; + } + } + tile = strtok_r(nullptr, ",", &preservedTile); + } + } + + mqttClientLastDataReceived = millis(); + break; // Break now - the dict topic should not be pushed + } + + // Is this the full AssistNow MGA data? If so, unsubscribe and subscribe to updates + if (strcmp(topic, MQTT_TOPIC_ASSISTNOW.c_str()) == 0) + { + std::vector::iterator pos = std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), MQTT_TOPIC_ASSISTNOW); + if (pos != mqttSubscribeTopics.end()) + mqttSubscribeTopics.erase(pos); + + mqttSubscribeTopics.push_back(MQTT_TOPIC_ASSISTNOW_UPDATES); + } + // Are these keys? If so, update our local copy - if (strstr(topic, settings.pointPerfectKeyDistributionTopic) != nullptr) + if ((strlen(settings.pointPerfectKeyDistributionTopic) > 0 ) + && (strcmp(topic, settings.pointPerfectKeyDistributionTopic) == 0)) { // Separate the UBX message into its constituent Key/ToW/Week parts uint8_t *payLoad = &mqttData[6]; @@ -389,7 +469,15 @@ void mqttClientReceiveMessage(int messageSize) if (present.gnss_zedf9p) { // Only push SPARTN if the priority says we can - if (strstr(topic, settings.regionalCorrectionTopics[settings.geographicRegion]) != nullptr) + if ( + // We can get correction data from the continental topic + ((strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) + && (strcmp(topic, settings.regionalCorrectionTopics[settings.geographicRegion]) == 0)) + || + // Or from the localized distribution tile topic + ((localizedDistributionTileTopic.length() > 0) + && (strcmp(topic, localizedDistributionTileTopic.c_str()) == 0)) + ) { // SPARTN updateCorrectionsLastSeen(CORR_IP); @@ -416,6 +504,10 @@ void mqttClientReceiveMessage(int messageSize) else { // KEYS or MGA + if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && + !inMainMenu) + systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); + gnssPushRawData(mqttData, mqttCount); bytesPushed += mqttCount; } @@ -581,6 +673,7 @@ void mqttClientUpdate() // Shutdown the MQTT client when the mode or setting changes DMW_st(mqttClientSetState, mqttClientState); + if (NEQ_RTK_MODE(mqttClientMode) || (!enableMqttClient)) { if (mqttClientState > MQTT_CLIENT_OFF) @@ -735,70 +828,38 @@ void mqttClientUpdate() // The MQTT server is now connected mqttClient->onMessage(mqttClientReceiveMessage); - reportHeapNow(settings.debugMqttClientState); - mqttClientSetState(MQTT_CLIENT_SUBSCRIBE_KEY); - break; - } + mqttSubscribeTopics.clear(); // Clear the list of MQTT topics to be subscribed to + mqttClientSubscribedTopics.clear(); // Clear the list of topics currently subscribed to + localizedDistributionDictTopic = ""; + localizedDistributionTileTopic = ""; - // Subscribe to the topic key - case MQTT_CLIENT_SUBSCRIBE_KEY: { - // Determine if the network has failed - if (networkIsShuttingDown(NETWORK_USER_MQTT_CLIENT)) + // Subscribe to AssistNow MGA if enabled + if (settings.useAssistNow) { - // Failed to connect to the network, attempt to restart the network - mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart - break; - } - - // Subscribe to the key distribution topic. This is provided during ZTP - if (!mqttClient->subscribe(settings.pointPerfectKeyDistributionTopic)) - { - mqttClientRestart(); - systemPrintln("ERROR: Subscription to key distribution topic failed!!"); - //mqttClientRestart(); // Why twice? TODO - break; + mqttSubscribeTopics.push_back(MQTT_TOPIC_ASSISTNOW); } - - if (settings.debugMqttClientState) - systemPrintln("MQTT client subscribed to key distribution topic"); - - mqttClientSetState(MQTT_CLIENT_SUBSCRIBE_SPARTN); - break; - } - - // Subscribe to the topic SPARTN - case MQTT_CLIENT_SUBSCRIBE_SPARTN: { - // Determine if the network has failed - if (networkIsShuttingDown(NETWORK_USER_MQTT_CLIENT)) - { - // Failed to connect to the network, attempt to restart the network - mqttClientStop(true); // Was mqttClientRestart(); - #StopVsRestart - break; - } - - // Subscribe to the correction topic for our region - if we have one. L-Band-only does not. + // Subscribe to the key distribution topic + mqttSubscribeTopics.push_back(String(settings.pointPerfectKeyDistributionTopic)); + // Subscribe to the continental correction topic for our region - if we have one. L-Band-only does not. if (strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) { - if (!mqttClient->subscribe(settings.regionalCorrectionTopics[settings.geographicRegion])) - { - mqttClientRestart(); - systemPrintln("ERROR: Subscription to corrections topic failed!!"); - //mqttClientRestart(); // Why twice? TODO - break; - } - - if (settings.debugMqttClientState) - systemPrintln("MQTT client subscribed to corrections topic"); + mqttSubscribeTopics.push_back(String(settings.regionalCorrectionTopics[settings.geographicRegion])); } else { if (settings.debugMqttClientState) systemPrintln("MQTT client - no corrections topic. Continuing..."); } + // Subscribing to the localized distribution dictionary and local tile is handled by MQTT_CLIENT_SERVICES_CONNECTED + // since we need a 3D fix for those + mqttClientLastDataReceived = millis(); // Prevent MQTT_CLIENT_SERVICES_CONNECTED from going immediately into timeout + + reportHeapNow(settings.debugMqttClientState); mqttClientSetState(MQTT_CLIENT_SERVICES_CONNECTED); + break; - } + } // /case MQTT_CLIENT_CONNECTING_2_SERVER case MQTT_CLIENT_SERVICES_CONNECTED: { // Determine if the network has failed @@ -817,9 +878,136 @@ void mqttClientUpdate() { systemPrintln("MQTT client data timeout. Disconnecting..."); mqttClientRestart(); + break; + } + + // Check if there are any new topics that should be subscribed to + bool breakOut = false; + for (auto it = mqttSubscribeTopics.begin(); it != mqttSubscribeTopics.end(); it = std::next(it)) + { + String topic = *it; + std::vector::iterator pos = std::find(mqttClientSubscribedTopics.begin(), mqttClientSubscribedTopics.end(), topic); + if (pos == mqttClientSubscribedTopics.end()) // The mqttSubscribeTopics is not in mqttClientSubscribedTopics, so subscribe + { + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client subscribing to topic %s\r\n", topic.c_str()); + if (mqttClient->subscribe(topic.c_str())) + { + breakOut = true; // Break out of this state as we have successfully subscribed + mqttClientSubscribedTopics.push_back(topic); + } + else + { + mqttClientRestart(); + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client subscription to topic %s failed. Restarting\r\n", topic.c_str()); + breakOut = true; // Break out of this state as the subscribe failed and a restart is needed + } + } + if (breakOut) + break; // Break out of the first for loop } + if (breakOut) + break; // Break out of this state + + // Check if there are any obsolete topics that should be unsubscribed + for (auto it = mqttClientSubscribedTopics.begin(); it != mqttClientSubscribedTopics.end(); it = std::next(it)) + { + String topic = *it; + std::vector::iterator pos = std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), topic); + if (pos == mqttSubscribeTopics.end()) // The mqttClientSubscribedTopics is not in mqttSubscribeTopics, so unsubscribe + { + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client unsubscribing from topic %s\r\n", topic.c_str()); + if (mqttClient->unsubscribe(topic.c_str())) + { + breakOut = true; // Break out of this state as we have successfully unsubscribed + mqttClientSubscribedTopics.erase(it); + } + else + { + mqttClientRestart(); + if (settings.debugMqttClientState) + systemPrintf("MQTT_Client unsubscribe from topic %s failed. Restarting\r\n", topic.c_str()); + breakOut = true; // Break out of this state as the subscribe failed and a restart is needed + } + } + if (breakOut) + break; // Break out of the first for loop + } + if (breakOut) + break; // Break out of this state + + // Check if localized distribution is enabled + if ((strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) && (settings.useLocalizedDistribution)) + { + uint8_t fixType = gnssGetFixType(); + double latitude = gnssGetLatitude(); // degrees + double longitude = gnssGetLongitude(); // degrees + if (fixType >= 3) // If we have a 3D fix + { + // If both the dict and tile topics are empty, prepare to subscribe to the dict topic + if ((localizedDistributionDictTopic.length() == 0) && (localizedDistributionTileTopic.length() == 0)) + { + float tileDelta = 2.5; // 2.5 degrees (10 degrees and 5 degrees are also possible) + if ((settings.localizedDistributionTileLevel == 0) || (settings.localizedDistributionTileLevel == 3)) + tileDelta = 10.0; + if ((settings.localizedDistributionTileLevel == 1) || (settings.localizedDistributionTileLevel == 4)) + tileDelta = 5.0; + + float lat = latitude; // Degrees + lat = floor(lat / tileDelta) * tileDelta; // Calculate the tile center in degrees + lat += tileDelta / 2.0; + int lat_i = round(lat * 100.0); // integer centidegrees + + float lon = longitude; // Degrees + lon = floor(lon / tileDelta) * tileDelta; // Calculate the tile center in degrees + lon += tileDelta / 2.0; + int lon_i = round(lon * 100.0); // integer centidegrees + + char dictTopic[50]; + snprintf(dictTopic, sizeof(dictTopic), "%s%c%c%04d%c%05d/dict", + localizedPrefix, char(0x30 + settings.localizedDistributionTileLevel), + (lat_i < 0) ? 'S' : 'N', abs(lat_i), + (lon_i < 0) ? 'W' : 'E', abs(lon_i)); + + + localizedDistributionDictTopic = dictTopic; + mqttSubscribeTopics.push_back(localizedDistributionDictTopic); + + breakOut = true; + } + + // localizedDistributionTileTopic is populated by mqttClientReceiveMessage + // If both the dict and tile topics are populated, prepare to subscribe to the localized tile topic + // Empty localizedDistributionDictTopic afterwardds to prevent this state being repeated + if ((localizedDistributionDictTopic.length() > 0) && (localizedDistributionTileTopic.length() > 0)) + { + // Subscribe to the localizedDistributionTileTopic + mqttSubscribeTopics.push_back(localizedDistributionTileTopic); + // Unsubscribe from the localizedDistributionDictTopic + std::vector::iterator pos = std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), localizedDistributionDictTopic); + if (pos != mqttSubscribeTopics.end()) + mqttSubscribeTopics.erase(pos); + // Unsubscribe from the continental corrections + pos = std::find(mqttSubscribeTopics.begin(), mqttSubscribeTopics.end(), String(settings.regionalCorrectionTopics[settings.geographicRegion])); + if (pos != mqttSubscribeTopics.end()) + mqttSubscribeTopics.erase(pos); + + localizedDistributionDictTopic = ""; // Empty localizedDistributionDictTopic to prevent this state being repeated + breakOut = true; + } + + + } + } + if (breakOut) + break; // Break out of this state + + + // Add any new state checking above this line break; - } + } // /case MQTT_CLIENT_SERVICES_CONNECTED } // Periodically display the MQTT client state diff --git a/Firmware/RTK_Everywhere/NtripClient.ino b/Firmware/RTK_Everywhere/NtripClient.ino index d58f6edb9..1f5b9dad2 100644 --- a/Firmware/RTK_Everywhere/NtripClient.ino +++ b/Firmware/RTK_Everywhere/NtripClient.ino @@ -833,7 +833,7 @@ void ntripClientUpdate() // Push RTCM to GNSS module over I2C / SPI gnssPushRawData(rtcmData, rtcmCount); - if ((settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && + if ((settings.debugCorrections || settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && (!inMainMenu)) { PERIODIC_CLEAR(PD_NTRIP_CLIENT_DATA); @@ -842,7 +842,7 @@ void ntripClientUpdate() } else { - if ((settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && + if ((settings.debugCorrections || settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && (!inMainMenu)) { PERIODIC_CLEAR(PD_NTRIP_CLIENT_DATA); diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index ac08aa92b..763fe443b 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -815,7 +815,6 @@ unsigned long lastGnssToPpl = 0; int commandCount; int16_t *commandIndex; -volatile bool forceKeyAttempt = false; // Set to true to force a key provisioning attempt //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // Display boot times diff --git a/Firmware/RTK_Everywhere/States.ino b/Firmware/RTK_Everywhere/States.ino index e0bec2aa1..8f7494157 100644 --- a/Firmware/RTK_Everywhere/States.ino +++ b/Firmware/RTK_Everywhere/States.ino @@ -503,7 +503,7 @@ void stateUpdate() break; case (STATE_KEYS_REQUESTED): { - forceKeyAttempt = true; // Force a key update + settings.requestKeyUpdate = true; // Force a key update changeState(lastSystemState); // Return to the last system state } break; diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index 59cd243a9..76402334b 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -270,7 +270,7 @@ void sendGnssBuffer() { if (gnssPushRawData(bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead)) { - if (PERIODIC_DISPLAY(PD_ZED_DATA_TX)) + if (settings.debugCorrections || PERIODIC_DISPLAY(PD_ZED_DATA_TX)) { PERIODIC_CLEAR(PD_ZED_DATA_TX); systemPrintf("Sent %d BT bytes to GNSS\r\n", bluetoothOutgoingToGnssHead); @@ -280,7 +280,7 @@ void sendGnssBuffer() } else { - if (PERIODIC_DISPLAY(PD_ZED_DATA_TX)) + if (settings.debugCorrections || PERIODIC_DISPLAY(PD_ZED_DATA_TX)) { PERIODIC_CLEAR(PD_ZED_DATA_TX); systemPrintf("%d BT bytes NOT sent due to priority\r\n", bluetoothOutgoingToGnssHead); diff --git a/Firmware/RTK_Everywhere/menuPP.ino b/Firmware/RTK_Everywhere/menuPP.ino index 903257014..e42c4da77 100644 --- a/Firmware/RTK_Everywhere/menuPP.ino +++ b/Firmware/RTK_Everywhere/menuPP.ino @@ -911,6 +911,12 @@ void menuPointPerfect() else systemPrintln("No keys"); + if ((settings.useLocalizedDistribution) && (localizedDistributionTileTopic.length() > 0)) + { + systemPrint("Most recent localized distribution topic: "); + systemPrintln(localizedDistributionTileTopic.c_str()); // From MQTT_Client.ino + } + // How this works: // There are three PointPerfect corrections plans: IP-only, L-Band-only, L-Band+IP // For IP-only - e.g. Torch: @@ -952,10 +958,31 @@ void menuPointPerfect() else systemPrintln("Disabled"); systemPrint("3) Request Key Update: "); - if (forceKeyAttempt == true) + if (settings.requestKeyUpdate == true) systemPrintln("Requested"); else systemPrintln("Not requested"); + systemPrint("4) Use localized distribution: "); + if (settings.useLocalizedDistribution == true) + systemPrintln("Enabled"); + else + systemPrintln("Disabled"); + if (settings.useLocalizedDistribution) + { + systemPrint("5) Localized distribution tile level: "); + systemPrint(settings.localizedDistributionTileLevel); + systemPrint(" ("); + systemPrint(localizedDistributionTileLevelNames[settings.localizedDistributionTileLevel]); + systemPrintln(")"); + } + if (productVariant != RTK_FACET_MOSAIC) + { + systemPrint("a) Use AssistNow: "); + if (settings.useAssistNow == true) + systemPrintln("Enabled"); + else + systemPrintln("Disabled"); + } #endif // COMPILE_NETWORK systemPrintln("c) Clear the Keys"); @@ -976,18 +1003,32 @@ void menuPointPerfect() { settings.enablePointPerfectCorrections ^= 1; restartRover = true; // Require a rover restart to enable / disable RTCM for PPL - forceKeyAttempt = settings.enablePointPerfectCorrections; // Force a key update - or don't + settings.requestKeyUpdate = settings.enablePointPerfectCorrections; // Force a key update - or don't } #ifdef COMPILE_NETWORK else if (incoming == 2 && pointPerfectIsEnabled()) { settings.autoKeyRenewal ^= 1; - forceKeyAttempt = settings.autoKeyRenewal; // Force a key update - or don't + settings.requestKeyUpdate = settings.autoKeyRenewal; // Force a key update - or don't } else if (incoming == 3 && pointPerfectIsEnabled()) { - forceKeyAttempt ^= 1; + settings.requestKeyUpdate ^= 1; + } + else if (incoming == 4 && pointPerfectIsEnabled()) + { + settings.useLocalizedDistribution ^= 1; + } + else if (incoming == 5 && pointPerfectIsEnabled() && settings.useLocalizedDistribution) + { + settings.localizedDistributionTileLevel++; + if (settings.localizedDistributionTileLevel >= LOCALIZED_DISTRIBUTION_TILE_LEVELS) + settings.localizedDistributionTileLevel = 0; + } + else if (incoming == 'a' && pointPerfectIsEnabled() && (productVariant != RTK_FACET_MOSAIC)) + { + settings.useAssistNow ^= 1; } #endif // COMPILE_NETWORK else if (incoming == 'c' && pointPerfectIsEnabled()) @@ -1170,7 +1211,7 @@ void provisioningSetState(uint8_t newState) } unsigned long provisioningStartTime; -const unsigned long provisioningTimeout = 20000; +const unsigned long provisioningTimeout = 120000; void updateProvisioning() { @@ -1197,13 +1238,13 @@ void updateProvisioning() { if ((online.rtc) || (millis() > (provisioningStartTime + provisioningTimeout)) - || (forceKeyAttempt)) + || (settings.requestKeyUpdate)) provisioningSetState(PROVISIONING_NOT_STARTED); } break; case PROVISIONING_NOT_STARTED: { - if (settings.enablePointPerfectCorrections && (settings.autoKeyRenewal || forceKeyAttempt)) + if (settings.enablePointPerfectCorrections && (settings.autoKeyRenewal || settings.requestKeyUpdate)) { provisioningSetState(PROVISIONING_CHECK_REMAINING); } @@ -1273,7 +1314,8 @@ void updateProvisioning() break; case PROVISIONING_STARTING: { - forceKeyAttempt = false; + settings.requestKeyUpdate = false; + recordSystemSettings(); // Record these settings to unit ztpResponse = ZTP_NOT_STARTED; // HTTP_Client will update this httpClientModeNeeded = true; // This will start the HTTP_Client provisioningStartTime = millis(); // Record the start time so we can timeout @@ -1360,6 +1402,14 @@ void updateProvisioning() httpClientModeNeeded = false; // Tell HTTP_Client to give up. (But it probably already has...) displayAlreadyRegistered(5000); + provisioningSetState(PROVISIONING_KEYS_REMAINING); + } + else if (ztpResponse == ZTP_UNKNOWN_ERROR) + { + systemPrintln("updateProvisioning: ZTP_UNKNOWN_ERROR"); + + httpClientModeNeeded = false; // Tell HTTP_Client to give up. (But it probably already has...) + provisioningSetState(PROVISIONING_KEYS_REMAINING); } } @@ -1395,12 +1445,14 @@ void updateProvisioning() break; case PROVISIONING_WAIT_ATTEMPT: { - if (forceKeyAttempt) + if (settings.requestKeyUpdate) provisioningSetState(PROVISIONING_STARTING); else if (!settings.enablePointPerfectCorrections || !settings.autoKeyRenewal) provisioningSetState(PROVISIONING_OFF); // When did we last try to get keys? Attempt every 24 hours - or every 15 mins for DEVELOPER - else if (online.rtc && (rtc.getEpoch() - settings.lastKeyAttempt > ( ENABLE_DEVELOPER ? (15 * 60) : (60 * 60 * 24)))) + //else if (online.rtc && (rtc.getEpoch() - settings.lastKeyAttempt > ( ENABLE_DEVELOPER ? (15 * 60) : (60 * 60 * 24)))) + // When did we last try to get keys? Attempt every 24 hours + else if (online.rtc && (rtc.getEpoch() - settings.lastKeyAttempt > (60 * 60 * 24))) { settings.lastKeyAttempt = rtc.getEpoch(); // Mark it recordSystemSettings(); // Record these settings to unit diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index d36e08d07..535ef7b1e 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -1369,10 +1369,27 @@ struct Settings bool outputTipAltitude = false; // If enabled, subtract the pole length and APC from the GNSS receiver's reported altitude + // Localized distribution + bool useLocalizedDistribution = false; + uint8_t localizedDistributionTileLevel = 5; + bool useAssistNow = false; + + bool requestKeyUpdate = false; // Set to true to force a key provisioning attempt + // Add new settings to appropriate group above or create new group // Then also add to the same group in rtkSettingsEntries below } settings; +const uint8_t LOCALIZED_DISTRIBUTION_TILE_LEVELS = 6; +const char *localizedDistributionTileLevelNames[LOCALIZED_DISTRIBUTION_TILE_LEVELS] = { + "1000 x 1000km sparse", + "500 x 500km sparse", + "250 x 250km sparse", + "1000 x 1000km high density", + "500 x 500km high density", + "250 x 250km high density", +}; + typedef enum { _bool = 0, _int, @@ -1863,6 +1880,13 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 1, 0, 1, 1, 1, 1, _uint32_t, 0, & settings.outputTipAltitude, "outputTipAltitude", }, + // Localized distribution + { 0, 1, 1, 0, 1, 1, 1, 1, _bool, 0, & settings.useLocalizedDistribution, "useLocalizedDistribution", }, + { 0, 1, 1, 0, 1, 1, 1, 1, _uint8_t, 0, & settings.localizedDistributionTileLevel, "localizedDistributionTileLevel", }, + { 0, 1, 1, 0, 1, 1, 0, 1, _bool, 0, & settings.useAssistNow, "useAssistNow", }, + + { 0, 1, 1, 0, 1, 1, 1, 1, _bool, 0, & settings.requestKeyUpdate, "requestKeyUpdate", }, + // Add new settings to appropriate group above or create new group // Then also add to the same group in settings above // F