├── .gitignore ├── LICENSE ├── README.md ├── examples ├── 1-simple │ └── 1-simple.cpp └── 2-button │ └── 2-button.cpp ├── library.properties ├── src ├── google-maps-device-locator.cpp └── google-maps-device-locator.h └── verification.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | target 3 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 rickkas7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Maps Device Locator 2 | 3 | **Library for using the Google Maps Device Locator Integration** 4 | 5 | This library work with the [Google Maps Device Locator Integration](https://docs.particle.io/tutorials/integrations/google-maps/) to find a location based on Wi-Fi or cellular tower information via the [Google Geolocation API](https://developers.google.com/maps/documentation/geolocation/intro). 6 | 7 | 8 | ## Firmware Library API 9 | 10 | ### Creating an object 11 | 12 | You normally create an locator object as a global variable in your program: 13 | 14 | ``` 15 | GoogleMapsDeviceLocator locator; 16 | ``` 17 | 18 | ### Operating modes 19 | 20 | There are three modes of operation: 21 | 22 | If you want to only publish the location once when the device starts up, use withLocateOnce from your setup function. 23 | 24 | ``` 25 | locator.withLocateOnce(); 26 | ``` 27 | 28 | To publish every *n* seconds while connected to the cloud, use withLocatePeriodic. The value is in seconds. 29 | 30 | ``` 31 | locator.withLocatePeriodic(120); 32 | ``` 33 | 34 | To manually connect, specify neither option and call publishLocation when you want to publish the location 35 | 36 | ``` 37 | locator.publishLocation(); 38 | ``` 39 | 40 | ### The loop 41 | 42 | With periodic and locate once modes, you must call 43 | 44 | ``` 45 | locator.loop(); 46 | ``` 47 | 48 | from your loop. It doesn't hurt to always call it, even in manual location mode. It gives the library time to process the data. 49 | 50 | ### Customizing the event name 51 | 52 | The default event name is **deviceLocator**. You can change that in setup using: 53 | 54 | ``` 55 | locator.withEventName("myEventName"); 56 | ``` 57 | 58 | This also must be updated in the integration, since the eventName is what triggers the webhook. 59 | 60 | ### Subscription 61 | 62 | You can also have the library tell your firmware code what location was found. Use the withSubscribe option with a callback function. 63 | 64 | This goes in setup() for example: 65 | 66 | ``` 67 | locator.withSubscribe(locationCallback).withLocatePeriodic(120); 68 | ``` 69 | 70 | The callback function looks like this: 71 | 72 | ``` 73 | void locationCallback(float lat, float lon, float accuracy) 74 | ``` 75 | 76 | One possibility is that you could display this information on a small OLED display, for example. 77 | 78 | ### Debugging 79 | 80 | The library uses the logging feature of system firmware 0.6.0 or later when building for 0.6.0 or later. Adding this line to the top of your .ino file will enable debugging messages to the serial port. 81 | 82 | ``` 83 | SerialLogHandler logHandler; 84 | ``` 85 | 86 | ### Special Notes for LTE (SARA-R410M-02B) 87 | 88 | For LTE devices, it's strongly recommended to use this library with Device OS 1.2.1 or later only. 89 | 90 | The Boron LTE and B Series B402 SoM both require 1.2.1 to use the device locator and will not work with older versions of Device OS. 91 | 92 | If you must use a version of Device OS older than 1.2.1: 93 | 94 | - The Boron LTE cannot use the device locator with Device OS older than 1.2.1 95 | - The E Series LTE and Electron LTE require the use of the `withOperator()` method to manually set the MCC, MNC, and operator if not 96 | 97 | The `withOperator(const char *oper, int mcc, int mnc)` method of the locator object allows you to pass in this information. The default is "AT&T", 310, 410. 98 | 99 | Note that because `withOperator()` is no longer needed with Device OS 1.2.1 and later, it will eventually be removed. 100 | 101 | 102 | ## Version History 103 | 104 | #### 0.0.8 (2024-09-26) 105 | 106 | - Remove Serial calls, use only Log calls 107 | - Update CellularHelper to 0.2.7 108 | 109 | #### 0.0.7 (2020-08-12) 110 | 111 | - Fix compiler warning with 2.0.0. 112 | 113 | #### 0.0.6 (2019-10-03) 114 | 115 | - Added support for the Boron LTE and B Series B402 SoM when using Device OS 1.2.1 and later. 116 | - The `withOperator()` function is no longer needed for LTE when using Device OS 1.2.1 and later. 117 | 118 | 119 | #### 0.0.5 (2018-11-27) 120 | 121 | - Added support for LTE in the United States, upgrade to CellularHelper 0.0.7 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /examples/1-simple/1-simple.cpp: -------------------------------------------------------------------------------- 1 | #include "google-maps-device-locator.h" 2 | 3 | // Comment this line out to disable debugging logs or change to LOG_LEVEL_INFO for fewer logs 4 | SerialLogHandler logHandler(LOG_LEVEL_TRACE); 5 | 6 | GoogleMapsDeviceLocator locator; 7 | 8 | void setup() { 9 | locator.withLocatePeriodic(120); 10 | } 11 | 12 | void loop() { 13 | locator.loop(); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /examples/2-button/2-button.cpp: -------------------------------------------------------------------------------- 1 | #include "google-maps-device-locator.h" 2 | 3 | // Comment this line out to disable debugging logs or change to LOG_LEVEL_INFO for fewer logs 4 | SerialLogHandler logHandler(LOG_LEVEL_TRACE); 5 | 6 | GoogleMapsDeviceLocator locator; 7 | const pin_t buttonPin = D2; 8 | unsigned long lastPublish = 0; 9 | const std::chrono::milliseconds minimumPublishPeriod = 20s; 10 | 11 | void setup() { 12 | pinMode(buttonPin, INPUT_PULLUP); 13 | } 14 | 15 | void loop() { 16 | locator.loop(); 17 | 18 | if (digitalRead(buttonPin) == LOW) { 19 | if (lastPublish == 0 || millis() - lastPublish >= minimumPublishPeriod.count()) { 20 | lastPublish = millis(); 21 | 22 | locator.publishLocation(); 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | # Fill in information about your library then remove # from the start of lines 2 | # https://docs.particle.io/guide/tools-and-features/libraries/#library-properties-fields 3 | name=google-maps-device-locator 4 | version=0.0.8 5 | author=rickkas7@rickkas7.com 6 | license=MIT 7 | sentence=Library for using Google Maps Device Locator Integration 8 | # paragraph=a longer description of this library 9 | url=https://github.com/spark/google-maps-device-locator 10 | repository=https://github.com/spark/google-maps-device-locator.git 11 | architectures=* 12 | dependencies.CellularHelper=0.2.7 13 | -------------------------------------------------------------------------------- /src/google-maps-device-locator.cpp: -------------------------------------------------------------------------------- 1 | #include "Particle.h" 2 | #include "google-maps-device-locator.h" 3 | 4 | #if Wiring_Cellular 5 | # include "CellularHelper.h" 6 | #endif 7 | 8 | static char requestBuf[256]; 9 | static char *requestCur; 10 | static int numAdded = 0; 11 | 12 | GoogleMapsDeviceLocator::GoogleMapsDeviceLocator() : locatorMode(LOCATOR_MODE_MANUAL), periodMs(10000), eventName("deviceLocator"), 13 | stateTime(0), state(CONNECT_WAIT_STATE), callback(NULL), waitAfterConnect(8000) { 14 | 15 | } 16 | 17 | GoogleMapsDeviceLocator::~GoogleMapsDeviceLocator() { 18 | 19 | } 20 | 21 | GoogleMapsDeviceLocator &GoogleMapsDeviceLocator::withLocateOnce() { 22 | locatorMode = LOCATOR_MODE_ONCE; 23 | return *this; 24 | } 25 | 26 | GoogleMapsDeviceLocator &GoogleMapsDeviceLocator::withLocatePeriodic(unsigned long secondsPeriodic) { 27 | locatorMode = LOCATOR_MODE_PERIODIC; 28 | if (secondsPeriodic < 5) { 29 | secondsPeriodic = 5; 30 | } 31 | periodMs = secondsPeriodic * 1000; 32 | return *this; 33 | } 34 | 35 | GoogleMapsDeviceLocator &GoogleMapsDeviceLocator::withEventName(const char *name) { 36 | this->eventName = name; 37 | return *this; 38 | } 39 | 40 | GoogleMapsDeviceLocator &GoogleMapsDeviceLocator::withSubscribe(GoogleMapsDeviceLocatorSubscriptionCallback callback) { 41 | this->callback = callback; 42 | 43 | snprintf(requestBuf, sizeof(requestBuf), "hook-response/%s/%s", eventName.c_str(), System.deviceID().c_str()); 44 | 45 | Particle.subscribe(requestBuf, &GoogleMapsDeviceLocator::subscriptionHandler, this, MY_DEVICES); 46 | 47 | return *this; 48 | } 49 | 50 | GoogleMapsDeviceLocator &GoogleMapsDeviceLocator::withOperator(const char *oper, int mcc, int mnc) { 51 | this->oper = oper; 52 | this->mcc = mcc; 53 | this->mnc = mnc; 54 | return *this; 55 | } 56 | 57 | 58 | 59 | void GoogleMapsDeviceLocator::loop() { 60 | switch(state) { 61 | case CONNECT_WAIT_STATE: 62 | if (Particle.connected()) { 63 | state = CONNECTED_WAIT_STATE; 64 | stateTime = millis(); 65 | } 66 | break; 67 | 68 | case CONNECTED_WAIT_STATE: 69 | if (millis() - stateTime >= waitAfterConnect) { 70 | // Wait several seconds after connecting before doing the location 71 | if (locatorMode == LOCATOR_MODE_ONCE) { 72 | publishLocation(); 73 | 74 | state = IDLE_STATE; 75 | } 76 | else 77 | if (locatorMode == LOCATOR_MODE_MANUAL) { 78 | state = IDLE_STATE; 79 | } 80 | else { 81 | state = CONNECTED_STATE; 82 | stateTime = millis() - periodMs; 83 | } 84 | } 85 | break; 86 | 87 | case CONNECTED_STATE: 88 | if (Particle.connected()) { 89 | if (millis() - stateTime >= periodMs) { 90 | stateTime = millis(); 91 | publishLocation(); 92 | } 93 | } 94 | else { 95 | // We have disconnected, rec 96 | state = CONNECT_WAIT_STATE; 97 | } 98 | break; 99 | 100 | 101 | case IDLE_STATE: 102 | // Just hang out here forever (entered only on LOCATOR_MODE_ONCE) 103 | break; 104 | } 105 | 106 | } 107 | 108 | const char *GoogleMapsDeviceLocator::scan() { 109 | #if Wiring_WiFi 110 | return wifiScan(); 111 | #endif 112 | #if Wiring_Cellular 113 | return cellularScan(); 114 | #endif 115 | } 116 | 117 | 118 | void GoogleMapsDeviceLocator::publishLocation() { 119 | 120 | const char *scanData = scan(); 121 | 122 | Log.info("publishLocation scanData=%s", scanData); 123 | 124 | if (scanData[0]) { 125 | 126 | if (Particle.connected()) { 127 | Particle.publish(eventName, scanData, PRIVATE); 128 | } 129 | } 130 | } 131 | 132 | void GoogleMapsDeviceLocator::subscriptionHandler(const char *event, const char *data) { 133 | // event: hook-response/deviceLocator//0 134 | 135 | if (callback) { 136 | // float lat, float lon, float accuracy 137 | char *mutableCopy = strdup(data); 138 | char *part, *end; 139 | float lat, lon, accuracy; 140 | 141 | part = strtok_r(mutableCopy, ",", &end); 142 | if (part) { 143 | lat = atof(part); 144 | part = strtok_r(NULL, ",", &end); 145 | if (part) { 146 | lon = atof(part); 147 | part = strtok_r(NULL, ",", &end); 148 | if (part) { 149 | accuracy = atof(part); 150 | 151 | (*callback)(lat, lon, accuracy); 152 | } 153 | } 154 | } 155 | 156 | free(mutableCopy); 157 | } 158 | } 159 | 160 | 161 | 162 | #if Wiring_WiFi 163 | 164 | static void wifiScanCallback(WiFiAccessPoint* wap, void* data) { 165 | // The - 3 factor here to leave room for the closing JSON array ] object }} and the trailing null 166 | size_t spaceLeft = &requestBuf[sizeof(requestBuf) - 3] - requestCur; 167 | 168 | size_t sizeNeeded = snprintf(requestCur, spaceLeft, 169 | "%s{\"m\":\"%02x:%02x:%02x:%02x:%02x:%02x\",\"s\":%d,\"c\":%d}", 170 | (requestCur[-1] == '[' ? "" : ","), 171 | wap->bssid[0], wap->bssid[1], wap->bssid[2], wap->bssid[3], wap->bssid[4], wap->bssid[5], 172 | wap->rssi, wap->channel); 173 | if (sizeNeeded <= spaceLeft) { 174 | // There is enough space to store the whole entry, so save it 175 | requestCur += sizeNeeded; 176 | numAdded++; 177 | } 178 | } 179 | 180 | 181 | const char *GoogleMapsDeviceLocator::wifiScan() { 182 | 183 | requestCur = requestBuf; 184 | numAdded = 0; 185 | 186 | requestCur += sprintf(requestCur, "{\"w\":{\"a\":"); 187 | *requestCur++ = '['; 188 | 189 | WiFi.scan(wifiScanCallback); 190 | 191 | *requestCur++ = ']'; 192 | *requestCur++ = '}'; 193 | *requestCur++ = '}'; 194 | *requestCur++ = 0; 195 | 196 | if (numAdded == 0) { 197 | requestBuf[0] = 0; 198 | } 199 | 200 | return requestBuf; 201 | } 202 | 203 | #endif /* Wiring_WiFi */ 204 | 205 | 206 | #if Wiring_Cellular 207 | 208 | static void cellularAddTower(const CellularHelperEnvironmentCellData *cellData) { 209 | // The - 4 factor here to leave room for the closing JSON array ], object }}, and the trailing null 210 | size_t spaceLeft = &requestBuf[sizeof(requestBuf) - 4] - requestCur; 211 | 212 | size_t sizeNeeded = snprintf(requestCur, spaceLeft, 213 | "%s{\"i\":%d,\"l\":%u,\"c\":%d,\"n\":%d}", 214 | (requestCur[-1] == '[' ? "" : ","), 215 | cellData->ci, cellData->lac, cellData->mcc, cellData->mnc); 216 | 217 | if (sizeNeeded <= spaceLeft && cellData->lac != 0 && cellData->lac != 65535 && cellData->mcc != 65535 && cellData->mnc != 65535) { 218 | // There is enough space to store the whole entry, so save it 219 | requestCur += sizeNeeded; 220 | numAdded++; 221 | } 222 | 223 | } 224 | 225 | #if HAS_CELLULAR_GLOBAL_IDENTITY 226 | const char *GoogleMapsDeviceLocator::cellularScanCGI() { 227 | 228 | *requestCur = 0; 229 | 230 | // getOperatorName (AT+UDOPN) is not supported on LTE (SARA-R410M-02-B) but the function 231 | // will return an empty string which is fine. 232 | String oper = CellularHelper.getOperatorName(); 233 | 234 | CellularGlobalIdentity cgi = {0}; 235 | cgi.size = sizeof(CellularGlobalIdentity); 236 | cgi.version = CGI_VERSION_LATEST; 237 | 238 | cellular_result_t res = cellular_global_identity(&cgi, NULL); 239 | if (res == SYSTEM_ERROR_NONE) { 240 | // We know these things fit, so just using sprintf instead of snprintf here 241 | requestCur += sprintf(requestCur, "{\"c\":{\"o\":\"%s\",", oper.c_str()); 242 | 243 | requestCur += sprintf(requestCur, "\"a\":["); 244 | 245 | requestCur += sprintf(requestCur, 246 | "{\"i\":%lu,\"l\":%u,\"c\":%u,\"n\":%u}", 247 | cgi.cell_id, cgi.location_area_code, cgi.mobile_country_code, cgi.mobile_network_code); 248 | 249 | numAdded++; 250 | 251 | *requestCur++ = ']'; 252 | *requestCur++ = '}'; 253 | *requestCur++ = '}'; 254 | *requestCur++ = 0; 255 | } 256 | else { 257 | // Log.info("cellular_global_identity failed %d", res); 258 | } 259 | 260 | return requestBuf; 261 | } 262 | #endif /* HAS_CELLULAR_GLOBAL_IDENTITY */ 263 | 264 | // This is only useful on the Electron and E Series LTE before Device OS 1.2.1. 265 | // It does not work on the Boron LTE. The cellular global identity (CGI) version 266 | // is better, and this will eventually be deprecated. 267 | const char *GoogleMapsDeviceLocator::cellularScanLTE() { 268 | 269 | CellularHelperCREGResponse resp; 270 | CellularHelper.getCREG(resp); 271 | 272 | // Log.info("cellularScanLTE %s", resp.toString().c_str()); 273 | 274 | // We know these things fit, so just using sprintf instead of snprintf here 275 | requestCur += sprintf(requestCur, "{\"c\":{\"o\":\"%s\",", oper.c_str()); 276 | 277 | requestCur += sprintf(requestCur, "\"a\":["); 278 | 279 | if (resp.valid) { 280 | requestCur += sprintf(requestCur, 281 | "{\"i\":%d,\"l\":%u,\"c\":%d,\"n\":%d}", 282 | resp.ci, resp.lac, mcc, mnc); 283 | 284 | numAdded++; 285 | } 286 | 287 | *requestCur++ = ']'; 288 | *requestCur++ = '}'; 289 | *requestCur++ = '}'; 290 | *requestCur++ = 0; 291 | 292 | 293 | 294 | if (numAdded == 0) { 295 | requestBuf[0] = 0; 296 | } 297 | 298 | return requestBuf; 299 | } 300 | 301 | 302 | const char *GoogleMapsDeviceLocator::cellularScan() { 303 | 304 | requestCur = requestBuf; 305 | numAdded = 0; 306 | 307 | #if HAS_CELLULAR_GLOBAL_IDENTITY 308 | { 309 | static bool modelChecked = false; 310 | static bool useCGI = false; 311 | 312 | if (!modelChecked) { 313 | modelChecked = true; 314 | 315 | // Use Cellular Global Identity (CGI) on Device OS 1.2.1 and later 316 | // if the modem is not a global 2G (G350). On the G350, AT+CGEG=5 317 | // works so a better multi-tower result can be returned. 318 | useCGI = !CellularHelper.getModel().startsWith("SARA-G350"); 319 | } 320 | if (useCGI) { 321 | return cellularScanCGI(); 322 | } 323 | } 324 | #endif 325 | 326 | if (CellularHelper.isLTE()) { 327 | return cellularScanLTE(); 328 | } 329 | 330 | // First try to get info on neighboring cells. This doesn't work for me using the U260 331 | CellularHelperEnvironmentResponseStatic<4> envResp; 332 | 333 | CellularHelper.getEnvironment(5, envResp); 334 | 335 | if (envResp.resp != RESP_OK) { 336 | // We couldn't get neighboring cells, so try just the receiving cell 337 | CellularHelper.getEnvironment(3, envResp); 338 | } 339 | // envResp.serialDebug(); 340 | 341 | 342 | // We know these things fit, so just using sprintf instead of snprintf here 343 | requestCur += sprintf(requestCur, "{\"c\":{\"o\":\"%s\",", 344 | CellularHelper.getOperatorName().c_str()); 345 | 346 | requestCur += sprintf(requestCur, "\"a\":["); 347 | 348 | cellularAddTower(&envResp.service); 349 | 350 | for(size_t ii = 0; ii < envResp.getNumNeighbors(); ii++) { 351 | cellularAddTower(&envResp.neighbors[ii]); 352 | } 353 | 354 | *requestCur++ = ']'; 355 | *requestCur++ = '}'; 356 | *requestCur++ = '}'; 357 | *requestCur++ = 0; 358 | 359 | if (numAdded == 0) { 360 | requestBuf[0] = 0; 361 | } 362 | 363 | return requestBuf; 364 | } 365 | 366 | 367 | #endif /* Wiring_Cellular */ 368 | 369 | 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /src/google-maps-device-locator.h: -------------------------------------------------------------------------------- 1 | #ifndef __GOOGLEMAPSDEVICELOCATOR_H 2 | #define __GOOGLEMAPSDEVICELOCATOR_H 3 | 4 | 5 | #include "Particle.h" 6 | 7 | #if defined(SYSTEM_VERSION_v121) && defined(Wiring_Cellular) 8 | #define HAS_CELLULAR_GLOBAL_IDENTITY 1 9 | #else 10 | #define HAS_CELLULAR_GLOBAL_IDENTITY 0 11 | #endif 12 | 13 | /** 14 | * This is the callback function prototype for the callback if you use the 15 | * withSubscribe() method to be notified of your own location. 16 | * 17 | * lat is the latitude in degrees 18 | * lon is the longitude in degrees 19 | * accuracy is the accuracy radius in meters 20 | */ 21 | typedef void (*GoogleMapsDeviceLocatorSubscriptionCallback)(float lat, float lon, float accuracy); 22 | 23 | class GoogleMapsDeviceLocator { 24 | public: 25 | GoogleMapsDeviceLocator(); 26 | virtual ~GoogleMapsDeviceLocator(); 27 | 28 | /** 29 | * If you use withLocateOnce() then the location will be updated once after you've 30 | * connected to the cloud. 31 | */ 32 | GoogleMapsDeviceLocator &withLocateOnce(); 33 | 34 | /** 35 | * If you use withLocatePeriod() then the location will be updated this often when 36 | * connected to the cloud. 37 | * 38 | * If you use neither withLocateOnce() nor withLocatePeriodic() you can check manually 39 | * using publishLocation() instead 40 | */ 41 | GoogleMapsDeviceLocator &withLocatePeriodic(unsigned long secondsPeriodic); 42 | 43 | /** 44 | * The default event name is "deviceLocator". Use this method to change it. Note that this 45 | * name is also configured in your Google location integration and must be changed in both 46 | * locations or location detection will not work 47 | */ 48 | GoogleMapsDeviceLocator &withEventName(const char *name); 49 | 50 | /** 51 | * Use this method to register a callback to be notified when your location has been found. 52 | * 53 | * The prototype for the function is: 54 | * 55 | * void locationCallback(float lat, float lon, float accuracy) 56 | */ 57 | GoogleMapsDeviceLocator &withSubscribe(GoogleMapsDeviceLocatorSubscriptionCallback callback); 58 | 59 | /** 60 | * Deprecated. No longer needed in Device OS 1.2.1 and later. 61 | * 62 | * Use this method to set the operator, mcc, and mnc when using LTE in Device OS prior to 1.2.1. 63 | * This does not work on the Boron LTE. 64 | * 65 | * The SARA-R410M-02B cannot get this information using AT+UCGED, so you need to pass it in 66 | * manually. Fortunately, in the United States with the Particle SIM it's always 67 | * "AT&T", 310, 410. 68 | */ 69 | GoogleMapsDeviceLocator &withOperator(const char *oper, int mcc, int mnc); 70 | 71 | /** 72 | * You should call this from loop() to give the code time to process things in the background. 73 | * This is really only needed if you use withLocateOnce() or withLocatePeriodic() but it doesn't 74 | * hurt to call it always from loop. It executes quickly. 75 | */ 76 | void loop(); 77 | 78 | /** 79 | * You can use this to manually publish your location. It finds the Wi-Fi or cellular location 80 | * using scan() and then publishes it as an event 81 | */ 82 | void publishLocation(); 83 | 84 | /** 85 | * Queries the location information (Wi-Fi or cellular) and returns a JSON block of data to 86 | * put in the event data. This returns a static buffer pointer and is not reentrant. 87 | */ 88 | const char *scan(); 89 | 90 | protected: 91 | void subscriptionHandler(const char *event, const char *data); 92 | 93 | #if Wiring_WiFi 94 | const char *wifiScan(); 95 | #endif 96 | 97 | #if HAS_CELLULAR_GLOBAL_IDENTITY 98 | const char *cellularScanCGI(); 99 | #endif 100 | 101 | #if Wiring_Cellular 102 | const char *cellularScanLTE(); 103 | const char *cellularScan(); 104 | #endif 105 | 106 | static const int CONNECT_WAIT_STATE = 0; 107 | static const int CONNECTED_WAIT_STATE = 2; 108 | static const int CONNECTED_STATE = 3; 109 | static const int IDLE_STATE = 4; 110 | 111 | static const int LOCATOR_MODE_MANUAL = 0; 112 | static const int LOCATOR_MODE_ONCE = 1; 113 | static const int LOCATOR_MODE_PERIODIC = 2; 114 | 115 | int locatorMode; 116 | unsigned long periodMs; 117 | String eventName; 118 | unsigned long stateTime; 119 | int state; 120 | GoogleMapsDeviceLocatorSubscriptionCallback callback; 121 | unsigned long waitAfterConnect; 122 | String oper = "AT&T"; // Used for LTE (SARA-R410M-02B only) 123 | int mcc = 310; // LTE 124 | int mnc = 410; // LTE 125 | }; 126 | 127 | #endif /* __GOOGLEMAPSDEVICELOCATOR_H */ 128 | 129 | -------------------------------------------------------------------------------- /verification.txt: -------------------------------------------------------------------------------- 1 | Google Maps Device Locator 2 | 3 | The main documentation for the library is here: 4 | https://docs.particle.io/tutorials/integrations/google-maps/ 5 | 6 | The device locator uses either Wi-Fi base station or Cellular tower information to do 7 | geolocation using Particle.publish and the Google Geolocation API. The Google Maps Device 8 | Locator Integration handles interfacing the publish and the Google API. 9 | 10 | Currently supported platforms: Photon, P1, and Electron 11 | Tested on system firmware versions: 0.6.1, 0.6.0 and 0.5.3 12 | 13 | Not supported: Core and Raspberry Pi (because they do not provide the necessary BSSID 14 | information), and Bluz (because it has neither Wi-Fi nor cellular). 15 | 16 | Testing 17 | ======= 18 | 19 | Run the examples/simple/simple.ino demo on a supported platform. 20 | 21 | Monitor the event stream. Every 2 minutes a deviceLocator event should be generated if 22 | connected to the Particle cloud. 23 | 24 | If you have enabled the Google Maps Device Locator integration as well, you'll also 25 | get back either: 26 | 27 | - A hook-response with the latitude,longitude,uncertainty with the uncertainty radius 28 | in meters 29 | - A 404 response if the location is not known by Google geolocation 30 | 31 | The output JSON in the event data should look similar to these examples: 32 | 33 | {"c":{"o":"","a":[{"i":43351,"l":43810,"c":310,"n":260}]}} 34 | 35 | {"c":{"o":"","a":[{"i":43351,"l":43810,"c":310,"n":260},{"i":145074106,"l":11511,"c":310,"n":410}]}} 36 | 37 | --------------------------------------------------------------------------------