Skip to content

Instantly share code, notes, and snippets.

@blindman2k
Last active February 15, 2016 05:00
Show Gist options
  • Save blindman2k/4d072838f6bbbcdd8b9b to your computer and use it in GitHub Desktop.
Save blindman2k/4d072838f6bbbcdd8b9b to your computer and use it in GitHub Desktop.
GoogleMaps Class for Electric Imp

GoogleMaps 1.0.0

The GoogleMaps class is an Electric Imp agent side library aimed at accessing the Google Maps API.

To add this library to your project, add #require "GoogleMaps.agent.nut:1.0.0" to the top of your agent code and #require "GoogleMaps.device.nut:1.0.0" to the top of your device code.

You can view the library's source code on GitHub.

See Google's API documentation for further information.

Class Usage

Constructor: GoogleMaps(api_key)

Apply for an API key on the Google Developer Console

Class Methods

scanWifi([callback])

This will request a wifi scan from the device by issuing a device.send() command. The device will respond with the wifi networks it can see. It will retry as many times as it needs until it gets a response from the device.

geolocate(wifis, callback)

This will take the wifi data from scanWifi() and send it to the Google Maps geolocation API. This API will try to return a geolocation based on the wifi signals it can see. The result will be returned as a table in the callback with fields:

Field Meaning
lat Latitude
lng Longitude
accuracy The accuracy radius of the estimated location, in meters

timezone(location, callback)

This will take the location data from geolocate() and send it to the Google Maps timezone API. This API will try to return a timezone data based on the geolocation provided. The result will be returned as a table in the callback with the following fields:

Field Meaning
status Status of the API query. Either OK or FAIL.
timeZoneId The name of the time zone. Refer to time zone list.
timeZoneName The long description of the time zone
gmtOffsetStr GMT Offset String such as GMT-7
rawOffset The time zone's offset without DST changes
dstOffset The DST offset to be added to the rawOffset to get the current gmtOffset
gmtOffset The time offset in seconds based on UTC time.
time Current local time in Unix timestamp.
date Squirrel date() object
dateStr Date string formatted as YYYY-MM-DD HH-MM-SS

Example

Agent code

#require "GoogleMaps.agent.nut:1.0.0"

gm <- GoogleMaps("apikey");
device.onconnect(function() {
    imp.wakeup(1, function() {
        gm.scanWifi(function(err, wifis) {
            if (err) return server.log(err);
            gm.geolocate(wifis, function(err, location) {
                if (err) return server.log(err);
                gm.timezone(location, function(err, tzdata) {
                    if (err) return server.log(err);
                    
                    server.log(format("[%f,%f] +/- %dm ==> %s %s", location.lat, location.lng, location.accuracy, tzdata.dateStr, tzdata.timeZoneId));
                });
            });
        });
    });
});

Device code

#require "GoogleMaps.device.nut:1.0.0"

License

The GoogleMaps class is licensed under the MIT License.

// Copyright (c) 2015 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT
// -----------------------------------------------------------------------------
class GoogleMaps {
_apikey = null;
_scanwifi_timer = null;
constructor(apikey) {
const WIFI_SCAN_TOPIC = "GOOGLEMAPS.WIFISCAN";
_apikey = apikey;
}
// Requests a wifi scan from the remote device
//
// The device code should look something like this:
//
// agent.on("wifiscan", function(d) {
// agent.send("wifiscan", imp.scanwifinetworks())
// });
//
function scanWifi(callback) {
// Stop any running scans
if (_scanwifi_timer) imp.cancelwakeup(_scanwifi_timer);
// Retry after timeout
_scanwifi_timer = imp.wakeup(30, function() {
_scanwifi_timer = null;
server.error("Timeout waiting for wifi scan");
scanWifi(callback)
}.bindenv(this));
// Setup the response handler
device.on(WIFI_SCAN_TOPIC, function(wifis) {
// Clear the timer
if (_scanwifi_timer) imp.cancelwakeup(_scanwifi_timer);
_scanwifi_timer = null;
// Remove the handler
device.on(WIFI_SCAN_TOPIC, function(d){});
// Done
callback(null, wifis);
}.bindenv(this));
// Send the actual request if we are connected
if (device.isconnected()) {
device.send(WIFI_SCAN_TOPIC, true);
}
return this;
}
// Takes a wifi scan from the device and converts it into a geolocation using google
//
// Returns a table containing:
// lat - Latitude
// lng - Longitude
// accuracy - Number of meters of accuracy
//
function geolocate(wifis, callback) {
if (wifis.len() < 2) return callback("Insufficient wifi signals");
local url = "https://www.googleapis.com/geolocation/v1/geolocate?key=" + _apikey;
local headers = {
"Content-Type": "application/json"
}
local request = {
"considerIp": false,
"wifiAccessPoints": []
}
// Convert the wifis into a format that google likes
foreach (wifi in wifis) {
local bssid = format("%s:%s:%s:%s:%s:%s", wifi.bssid.slice(0,2),
wifi.bssid.slice(2,4),
wifi.bssid.slice(4,6),
wifi.bssid.slice(6,8),
wifi.bssid.slice(8,10),
wifi.bssid.slice(10,12));
local newwifi = {};
newwifi.macAddress <- bssid.toupper();
newwifi.signalStrength <- wifi.rssi;
newwifi.channel <- wifi.channel;
request.wifiAccessPoints.push(newwifi);
}
// Post the request
http.post(url, headers, http.jsonencode(request)).sendasync(function(res) {
// Parse the response
local body = null;
try {
body = http.jsondecode(res.body);
} catch (e) {
callback(e, null);
return;
}
if (res.statuscode == 200 && "location" in body) {
// All looking good
local location = {};
location.accuracy <- body.accuracy;
location.lat <- body.location.lat;
location.lng <- body.location.lng;
callback(null, location);
} else if (res.statuscode == 429) {
// We have been throttled. try again in a second
imp.wakeup(1, function() {
geolocate(wifis, callback);
}.bindenv(this));
} else if ("message" in body) {
// Return Google's error message
callback(body.message, null);
} else {
// Return the HTTP status code
callback("Error " + res.statuscode, null);
}
}.bindenv(this));
return this;
}
// Request timezone data from Google by supplying just a lat/lng pair.
//
// Returns a table containing:
// status Status of the API query. Either OK or FAIL.
// timeZoneId The name of the time zone. Refer to time zone list.
// timeZoneName The long description of the time zone
// gmtOffsetStr GMT Offset String such as GMT-7
// rawOffset The time zone's offset without DST changes
// dstOffset The DST offset to be added to the rawOffset to get the current gmtOffset
// gmtOffset The time offset in seconds based on UTC time.
// time Current local time in Unix timestamp.
// date Squirrel date() object
// dateStr Date string formatted as YYYY-MM-DD HH-MM-SS
//
function timezone(location, callback) {
assert("lat" in location && "lng" in location);
// POST the location data to Google and wait for a response
local url = format("https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f&timestamp=%d", location.lat, location.lng, time());
local headers = {};
http.get(url, headers).sendasync(function(res) {
// Parse the response
local body = null;
try {
body = http.jsondecode(res.body);
} catch (e) {
callback(e, null);
return;
}
if (!("status" in body)) {
// The response has no status, this must be an error
callback("Error " + res.statuscode, null);
} else if (body.status == "OK") {
// Success
local t = time() + body.rawOffset + body.dstOffset;
local d = date(t);
body.time <- t;
body.date <- d;
body.dateStr <- format("%04d-%02d-%02d %02d:%02d:%02d", d.year, d.month+1, d.day, d.hour, d.min, d.sec)
body.gmtOffset <- body.rawOffset + body.dstOffset;
body.gmtOffsetStr <- format("GMT%s%d", body.gmtOffset < 0 ? "-" : "+", math.abs(body.gmtOffset / 3600));
callback(null, body);
} else {
// Failure
callback("Error " + body.status, null);
}
}.bindenv(this));
return this;
}
}
// Copyright (c) 2015 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT
// -----------------------------------------------------------------------------
function GoogleMaps() {
// Responds to a request for a wifi scan with the results of the wifi scan. Note its blocking.
const WIFI_SCAN_TOPIC = "GOOGLEMAPS.WIFISCAN";
agent.on(WIFI_SCAN_TOPIC, function(d) {
agent.send(WIFI_SCAN_TOPIC, imp.scanwifinetworks())
});
}
GoogleMaps();
The MIT License (MIT)
Copyright (c) 2015 Electric Imp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@betzrhodes
Copy link

README typo - wifi scan callback is not an optional parameter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment