|
| 1 | +/* |
| 2 | + Use ESP32 WiFi to get AssistNow Offline data from u-blox Thingstream |
| 3 | + By: SparkFun Electronics / Paul Clark |
| 4 | + Date: November 26th, 2021 |
| 5 | + License: MIT. See license file for more information but you can |
| 6 | + basically do whatever you want with this code. |
| 7 | +
|
| 8 | + This example shows how to obtain AssistNow Offline data from u-blox Thingstream over WiFi |
| 9 | + and push it over I2C to a u-blox module. |
| 10 | +
|
| 11 | + The module still needs to be given time assistance to achieve a fast fix. This example |
| 12 | + uses network time to do that. If you don't have a WiFi connection, you may have to use |
| 13 | + a separate RTC to provide the time. |
| 14 | +
|
| 15 | + Note: AssistNow Offline is not supported by the ZED-F9P! "The ZED-F9P supports AssistNow Online only." |
| 16 | +
|
| 17 | + You will need to have a token to be able to access Thingstream. See the AssistNow README for more details. |
| 18 | +
|
| 19 | + Update secrets.h with your: |
| 20 | + - WiFi credentials |
| 21 | + - AssistNow token string |
| 22 | +
|
| 23 | + Uncomment the "#define USE_MGA_ACKs" below to test the more robust method of using the |
| 24 | + UBX_MGA_ACK_DATA0 acknowledgements to confirm that each MGA message has been accepted. |
| 25 | +
|
| 26 | + Feel like supporting open source hardware? |
| 27 | + Buy a board from SparkFun! |
| 28 | + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 |
| 29 | + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 |
| 30 | +
|
| 31 | + Hardware Connections: |
| 32 | + Plug a Qwiic cable into the GNSS and a ESP32 Thing Plus |
| 33 | + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) |
| 34 | + Open the serial monitor at 115200 baud to see the output |
| 35 | +*/ |
| 36 | + |
| 37 | +//#define USE_MGA_ACKs // Uncomment this line to use the UBX_MGA_ACK_DATA0 acknowledgements |
| 38 | + |
| 39 | +#include <WiFi.h> |
| 40 | +#include <HTTPClient.h> |
| 41 | +#include "secrets.h" |
| 42 | + |
| 43 | +const char assistNowServer[] = "https://offline-live1.services.u-blox.com"; |
| 44 | +//const char assistNowServer[] = "https://offline-live2.services.u-blox.com"; // Alternate server |
| 45 | + |
| 46 | +const char getQuery[] = "GetOfflineData.ashx?"; |
| 47 | +const char tokenPrefix[] = "token="; |
| 48 | +const char tokenSuffix[] = ";"; |
| 49 | +const char getGNSS[] = "gnss=gps,glo;"; // GNSS can be: gps,qzss,glo,bds,gal |
| 50 | +const char getFormat[] = "format=mga;"; // Data format. Leave set to mga for M8 onwards. Can be aid. |
| 51 | +const char getPeriod[] = "period=1;"; // Optional. The number of weeks into the future that the data will be valid. Can be 1-5. Default = 4. |
| 52 | +const char getMgaResolution[] = "resolution=1;"; // Optional. Data resolution: 1 = every day; 2 = every other day; 3 = every 3rd day. |
| 53 | +//Note: always use resolution=1. findMGAANOForDate does not yet support finding the 'closest' date. It needs an exact match. |
| 54 | + |
| 55 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 56 | + |
| 57 | +#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS |
| 58 | +SFE_UBLOX_GNSS myGNSS; |
| 59 | + |
| 60 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 61 | + |
| 62 | +#include "time.h" |
| 63 | + |
| 64 | +const char* ntpServer = "pool.ntp.org"; // The Network Time Protocol Server |
| 65 | + |
| 66 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 67 | + |
| 68 | +void setup() |
| 69 | +{ |
| 70 | + delay(1000); |
| 71 | + |
| 72 | + Serial.begin(115200); |
| 73 | + Serial.println(F("AssistNow Example")); |
| 74 | + |
| 75 | + while (Serial.available()) Serial.read(); // Empty the serial buffer |
| 76 | + Serial.println(F("Press any key to begin...")); |
| 77 | + while (!Serial.available()); // Wait for a keypress |
| 78 | + |
| 79 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 80 | + // Start I2C. Connect to the GNSS. |
| 81 | + |
| 82 | + Wire.begin(); //Start I2C |
| 83 | + |
| 84 | + if (myGNSS.begin() == false) //Connect to the Ublox module using Wire port |
| 85 | + { |
| 86 | + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); |
| 87 | + while (1); |
| 88 | + } |
| 89 | + Serial.println(F("u-blox module connected")); |
| 90 | + |
| 91 | + myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise |
| 92 | + |
| 93 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 94 | + // Connect to WiFi. |
| 95 | + |
| 96 | + Serial.print(F("Connecting to local WiFi")); |
| 97 | + |
| 98 | + WiFi.begin(ssid, password); |
| 99 | + while (WiFi.status() != WL_CONNECTED) { |
| 100 | + delay(500); |
| 101 | + Serial.print(F(".")); |
| 102 | + } |
| 103 | + Serial.println(); |
| 104 | + |
| 105 | + Serial.println(F("WiFi connected!")); |
| 106 | + |
| 107 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 108 | + // Set the RTC using network time. (Code taken from the SimpleTime example.) |
| 109 | + |
| 110 | + // Request the time from the NTP server and use it to set the ESP32's RTC. |
| 111 | + configTime(0, 0, ntpServer); // Set the GMT and daylight offsets to zero. We need UTC, not local time. |
| 112 | + |
| 113 | + struct tm timeinfo; |
| 114 | + if(!getLocalTime(&timeinfo)) |
| 115 | + { |
| 116 | + Serial.println("Failed to obtain time"); |
| 117 | + return; |
| 118 | + } |
| 119 | + Serial.println(&timeinfo, "Time is: %A, %B %d %Y %H:%M:%S"); |
| 120 | + |
| 121 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 122 | + // Use HTTP GET to receive the AssistNow_Online data |
| 123 | + |
| 124 | + const int URL_BUFFER_SIZE = 256; |
| 125 | + char theURL[URL_BUFFER_SIZE]; // This will contain the HTTP URL |
| 126 | + int payloadSize = 0; // This will be updated with the length of the data we get from the server |
| 127 | + String payload; // This will store the data we get from the server |
| 128 | + |
| 129 | + // Assemble the URL |
| 130 | + // Note the slash after the first %s (assistNowServer) |
| 131 | + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s%s%s", |
| 132 | + assistNowServer, |
| 133 | + getQuery, |
| 134 | + tokenPrefix, |
| 135 | + myAssistNowToken, |
| 136 | + tokenSuffix, |
| 137 | + getGNSS, |
| 138 | + getFormat, |
| 139 | + getPeriod, |
| 140 | + getMgaResolution |
| 141 | + ); |
| 142 | + |
| 143 | + Serial.print(F("HTTP URL is: ")); |
| 144 | + Serial.println(theURL); |
| 145 | + |
| 146 | + HTTPClient http; |
| 147 | + |
| 148 | + http.begin(theURL); |
| 149 | + |
| 150 | + int httpCode = http.GET(); // HTTP GET |
| 151 | + |
| 152 | + // httpCode will be negative on error |
| 153 | + if(httpCode > 0) |
| 154 | + { |
| 155 | + // HTTP header has been sent and Server response header has been handled |
| 156 | + Serial.printf("[HTTP] GET... code: %d\r\n", httpCode); |
| 157 | + |
| 158 | + // If the GET was successful, read the data |
| 159 | + if(httpCode == HTTP_CODE_OK) // Check for code 200 |
| 160 | + { |
| 161 | + payloadSize = http.getSize(); |
| 162 | + Serial.printf("Server returned %d bytes\r\n", payloadSize); |
| 163 | + |
| 164 | + payload = http.getString(); // Get the payload |
| 165 | + |
| 166 | + // Pretty-print the payload as HEX |
| 167 | + /* |
| 168 | + int i; |
| 169 | + for(i = 0; i < payloadSize; i++) |
| 170 | + { |
| 171 | + if (payload[i] < 0x10) // Print leading zero |
| 172 | + Serial.print("0"); |
| 173 | + Serial.print(payload[i], HEX); |
| 174 | + Serial.print(" "); |
| 175 | + if ((i % 16) == 15) |
| 176 | + Serial.println(); |
| 177 | + } |
| 178 | + if ((i % 16) != 15) |
| 179 | + Serial.println(); |
| 180 | + */ |
| 181 | + } |
| 182 | + } |
| 183 | + else |
| 184 | + { |
| 185 | + Serial.printf("[HTTP] GET... failed, error: %s\r\n", http.errorToString(httpCode).c_str()); |
| 186 | + } |
| 187 | + |
| 188 | + http.end(); |
| 189 | + |
| 190 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 191 | + // Find where the AssistNow data for today starts and ends |
| 192 | + |
| 193 | + size_t todayStart = 0; // Default to sending all the data |
| 194 | + size_t tomorrowStart = (size_t)payloadSize; |
| 195 | + |
| 196 | + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent |
| 197 | + //myGNSS.enableDebugging(Serial, true); |
| 198 | + |
| 199 | + if (payloadSize > 0) |
| 200 | + { |
| 201 | + if(getLocalTime(&timeinfo)) |
| 202 | + { |
| 203 | + // Find the start of today's data |
| 204 | + todayStart = myGNSS.findMGAANOForDate(payload, (size_t)payloadSize, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday); |
| 205 | + if (todayStart < (size_t)payloadSize) |
| 206 | + { |
| 207 | + Serial.print(F("Found the data for today starting at location ")); |
| 208 | + Serial.println(todayStart); |
| 209 | + } |
| 210 | + else |
| 211 | + { |
| 212 | + Serial.println("Could not find the data for today. This will not work well. The GNSS needs help to start up quickly."); |
| 213 | + } |
| 214 | + |
| 215 | + // Find the start of tomorrow's data |
| 216 | + tomorrowStart = myGNSS.findMGAANOForDate(payload, (size_t)payloadSize, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, 1); |
| 217 | + if (tomorrowStart < (size_t)payloadSize) |
| 218 | + { |
| 219 | + Serial.print(F("Found the data for tomorrow starting at location ")); |
| 220 | + Serial.println(tomorrowStart); |
| 221 | + } |
| 222 | + else |
| 223 | + { |
| 224 | + Serial.println("Could not find the data for tomorrow. (Today's data may be the last?)"); |
| 225 | + } |
| 226 | + } |
| 227 | + else |
| 228 | + { |
| 229 | + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 234 | + // Push the RTC time to the module |
| 235 | + |
| 236 | + if(getLocalTime(&timeinfo)) // Get the local time again, just to make sure we are using the most accurate time |
| 237 | + { |
| 238 | + // setUTCTimeAssistance uses a default time accuracy of 2 seconds which should be OK here. |
| 239 | + // Have a look at the library source code for more details. |
| 240 | + myGNSS.setUTCTimeAssistance(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, |
| 241 | + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); |
| 242 | + } |
| 243 | + else |
| 244 | + { |
| 245 | + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); |
| 246 | + } |
| 247 | + |
| 248 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 249 | + // Push the AssistNow data for today to the module - without the time |
| 250 | + |
| 251 | + if (payloadSize > 0) |
| 252 | + { |
| 253 | + |
| 254 | +#ifndef USE_MGA_ACKs |
| 255 | + |
| 256 | + // ***** Don't use the UBX_MGA_ACK_DATA0 messages ***** |
| 257 | + |
| 258 | + // Push the AssistNow data for today. Don't use UBX_MGA_ACK_DATA0's. Use the default delay of 7ms between messages. |
| 259 | + myGNSS.pushAssistNowData(todayStart, true, payload, tomorrowStart - todayStart); |
| 260 | + |
| 261 | +#else |
| 262 | + |
| 263 | + // ***** Use the UBX_MGA_ACK_DATA0 messages ***** |
| 264 | + |
| 265 | + // Tell the module to return UBX_MGA_ACK_DATA0 messages when we push the AssistNow data |
| 266 | + myGNSS.setAckAiding(1); |
| 267 | + |
| 268 | + // Speed things up by setting setI2CpollingWait to 1ms |
| 269 | + myGNSS.setI2CpollingWait(1); |
| 270 | + |
| 271 | + // Push the AssistNow data for today. |
| 272 | + myGNSS.pushAssistNowData(todayStart, true, payload, tomorrowStart - todayStart, SFE_UBLOX_MGA_ASSIST_ACK_YES, 100); |
| 273 | + |
| 274 | + // Set setI2CpollingWait to 125ms to avoid pounding the I2C bus |
| 275 | + myGNSS.setI2CpollingWait(125); |
| 276 | + |
| 277 | +#endif |
| 278 | + |
| 279 | + } |
| 280 | + |
| 281 | + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 282 | + // Disconnect the WiFi as it's no longer needed |
| 283 | + |
| 284 | + WiFi.disconnect(true); |
| 285 | + WiFi.mode(WIFI_OFF); |
| 286 | + Serial.println(F("WiFi disconnected")); |
| 287 | +} |
| 288 | + |
| 289 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 290 | + |
| 291 | +void loop() |
| 292 | +{ |
| 293 | + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D |
| 294 | + |
| 295 | + long latitude = myGNSS.getLatitude(); |
| 296 | + Serial.print(F("Lat: ")); |
| 297 | + Serial.print(latitude); |
| 298 | + |
| 299 | + long longitude = myGNSS.getLongitude(); |
| 300 | + Serial.print(F(" Long: ")); |
| 301 | + Serial.print(longitude); |
| 302 | + Serial.print(F(" (degrees * 10^-7)")); |
| 303 | + |
| 304 | + long altitude = myGNSS.getAltitude(); |
| 305 | + Serial.print(F(" Alt: ")); |
| 306 | + Serial.print(altitude); |
| 307 | + Serial.print(F(" (mm)")); |
| 308 | + |
| 309 | + byte SIV = myGNSS.getSIV(); |
| 310 | + Serial.print(F(" SIV: ")); |
| 311 | + Serial.print(SIV); |
| 312 | + |
| 313 | + byte fixType = myGNSS.getFixType(); |
| 314 | + Serial.print(F(" Fix: ")); |
| 315 | + if(fixType == 0) Serial.print(F("No fix")); |
| 316 | + else if(fixType == 1) Serial.print(F("Dead reckoning")); |
| 317 | + else if(fixType == 2) Serial.print(F("2D")); |
| 318 | + else if(fixType == 3) Serial.print(F("3D")); |
| 319 | + else if(fixType == 4) Serial.print(F("GNSS + Dead reckoning")); |
| 320 | + else if(fixType == 5) Serial.print(F("Time only")); |
| 321 | + |
| 322 | + Serial.println(); |
| 323 | +} |
0 commit comments