Last active
February 1, 2019 08:39
-
-
Save FishOfPrey/46a21880092754d3ca7f5935eb2f017e to your computer and use it in GitHub Desktop.
Electric Imp SmartFridge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// | |
// Copyright 2017 Electric Imp | |
// | |
// SPDX-License-Identifier: MIT | |
// | |
// 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. | |
// Utility Libraries | |
#require "Rocky.class.nut:1.2.3" | |
// Web Integration Library - https://github.com/electricimp/Salesforce | |
#require "Salesforce.class.nut:1.1.0" | |
// Extends Salesforce Library to handle authorization | |
class SalesforceOAuth2 extends Salesforce { | |
_login = null; | |
constructor(consumerKey, consumerSecret, loginServiceBase = null, salesforceVersion = null) { | |
_clientId = consumerKey; | |
_clientSecret = consumerSecret; | |
if ("Rocky" in getroottable()) { | |
_login = Rocky(); | |
} else { | |
throw "Unmet dependency: SalesforceOAuth2 requires Rocky"; | |
} | |
if (loginServiceBase != null) _loginServiceBase = loginServiceBase; | |
if (salesforceVersion != null) _version = salesforceVersion; | |
getStoredCredentials(); | |
defineLoginEndpoint(); | |
} | |
function getStoredCredentials() { | |
local persist = server.load(); | |
local oAuth = {}; | |
if ("oAuth" in persist) oAuth = persist.oAuth; | |
// Load credentials if we have them | |
if ("instance_url" in oAuth && "access_token" in oAuth) { | |
// Set the credentials in the Salesforce object | |
setInstanceUrl(oAuth.instance_url); | |
setToken(oAuth.access_token); | |
server.log(format("instance_url: %s access_token %s", | |
oAuth.instance_url, oAuth.access_token)); | |
// Log a message | |
server.log("Loaded OAuth Credentials!"); | |
} | |
} | |
function defineLoginEndpoint() { | |
// Define log in endpoint for a GET request to the agent URL | |
_login.get("/", function(context) { | |
// Check if an OAuth code was passed in | |
if (!("code" in context.req.query)) { | |
// If it wasn't, redirect to login service | |
local location = format( | |
"%s/services/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s", | |
_loginServiceBase, | |
_clientId, http.agenturl()); | |
context.setHeader("Location", location); | |
context.send(302, "Found"); | |
return; | |
} | |
// Exchange the auth code for inan OAuth token | |
getOAuthToken(context.req.query["code"], function(err, resp, respData) { | |
if (err) { | |
context.send(400, "Error authenticating (" + err + ")."); | |
return; | |
} | |
// If it was successful, save the data locally | |
local persist = { "oAuth" : respData }; | |
server.save(persist); | |
// Set/update the credentials in the Salesforce object | |
setInstanceUrl(persist.oAuth.instance_url); | |
setToken(persist.oAuth.access_token); | |
// Finally - inform the user we're done! | |
context.send(200, "Authentication complete - you may now close this window"); | |
}.bindenv(this)); | |
}.bindenv(this)); | |
} | |
// OAuth 2.0 methods | |
function getOAuthToken(code, cb) { | |
// Send request with an authorization code | |
_oauthTokenRequest("authorization_code", code, cb); | |
} | |
function refreshOAuthToken(refreshToken, cb) { | |
// Send request with refresh token | |
_oauthTokenRequest("refresh_token", refreshToken, cb); | |
} | |
function _oauthTokenRequest(type, tokenCode, cb = null) { | |
// Build the request | |
local url = format("%s/services/oauth2/token", _loginServiceBase); | |
local headers = { "Content-Type": "application/x-www-form-urlencoded" }; | |
local data = { | |
"grant_type": type, | |
"client_id": _clientId, | |
"client_secret": _clientSecret, | |
}; | |
// Set the "code" or "refresh_token" parameters based on grant_type | |
if (type == "authorization_code") { | |
data.code <- tokenCode; | |
data.redirect_uri <- http.agenturl(); | |
} else if (type == "refresh_token") { | |
data.refresh_token <- tokenCode; | |
} else { | |
throw "Unknown grant_type"; | |
} | |
local body = http.urlencode(data); | |
http.post(url, headers, body).sendasync(function(resp) { | |
local respData = http.jsondecode(resp.body); | |
local err = null; | |
// If there was an error, set the error code | |
if (resp.statuscode != 200) err = data.message; | |
// Invoke the callback | |
if (cb) { | |
imp.wakeup(0, function() { | |
cb(err, resp, respData); | |
}); | |
} | |
}); | |
} | |
} | |
// Door status strings | |
const DOOR_OPEN = "open"; | |
const DOOR_CLOSED = "closed"; | |
// Application code, listen for readings from device, | |
// when a reading is received send the data to Salesforce | |
class SmartFridgeApplication { | |
_force = null; | |
_deviceID = null; | |
_sendReadingUrl = null; | |
constructor(key, secret, readingEventName) { | |
_deviceID = imp.configparams.deviceid.tostring(); | |
_sendReadingUrl = format("sobjects/%s/", readingEventName); | |
_force = SalesforceOAuth2(key, secret, null, "v40.0"); | |
device.on("reading", readingHandler.bindenv(this)); | |
} | |
// Sends the data received from device, to Salesforce as Platform Event. | |
function readingHandler(data) { | |
local body = { "deviceId__c" : _deviceID }; | |
// add Salesforce fields postfix to data keys and convert values if needed | |
foreach (key, value in data) { | |
if (key == "ts") { | |
value = formatTimestamp(value); | |
} | |
if (key == "doorOpen") { | |
key = "door"; | |
value = value ? DOOR_OPEN : DOOR_CLOSED; | |
} | |
body[key + "__c"] <- value; | |
} | |
// don't send if we are not logged in | |
if (!_force.isLoggedIn()) { | |
server.error("Not logged into Salesforce.") | |
return; | |
} | |
// Log the data being sent to the cloud | |
server.log(http.jsonencode(body)); | |
// Send Salesforce platform event with device readings | |
_force.request("POST", _sendReadingUrl, http.jsonencode(body), function (err, respData) { | |
if (err) { | |
server.error(http.jsonencode(err)); | |
// Clear stored OAuth details | |
//server.save({}); | |
//server.error("Cleared stored OAuth details. Init OAuth from Agent URL"); | |
} | |
else { | |
server.log("Readings sent successfully"); | |
} | |
}); | |
} | |
// Converts timestamp to "2017-12-03T00:54:51Z" format | |
function formatTimestamp(ts = null) { | |
local d = ts ? date(ts) : date(); | |
return format("%04d-%02d-%02dT%02d:%02d:%02dZ", d.year, d.month + 1, d.day, d.hour, d.min, d.sec); | |
} | |
} | |
// RUNTIME | |
// --------------------------------------------------------------------------------- | |
// SALESFORCE CONSTANTS | |
// ---------------------------------------------------------- | |
const CONSUMER_KEY = "@{SALESFORCE_CONSUMER_KEY}"; | |
const CONSUMER_SECRET = "@{SALESFORCE_CONSUMER_SECRET}"; | |
const READING_EVENT_NAME = "Smart_Fridge_Reading__e"; | |
// Start Application | |
SmartFridgeApplication(CONSUMER_KEY, CONSUMER_SECRET, READING_EVENT_NAME); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// | |
// Copyright 2017 Electric Imp | |
// | |
// SPDX-License-Identifier: MIT | |
// | |
// 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. | |
// Temperature Humidity sensor Library | |
#require "HTS221.device.lib.nut:2.0.1" | |
// pressure sensor Library | |
#require "LPS22HB.device.lib.nut:2.0.0" | |
// three-axis MEMS accelerometer | |
#require "LIS3DH.device.lib.nut:2.0.1" | |
// RGB LED | |
#require "WS2812.class.nut:3.0.0" | |
// Class to configure and take readings from Explorer Kit sensors. | |
// Take readings from temperature humidity and light sensors. | |
// Use the light level to determine if the door is open (true or false) | |
// and send the door status, temperature and humidity to the agent | |
class SmartFridge { | |
// Time in seconds to wait between readings | |
static READING_INTERVAL_SEC = 1; | |
// The lx level at which we know the door is open | |
static LX_THRESHOLD = 9000; | |
// Sensor variables | |
tempHumid = null; | |
pressureSensor = null; | |
accel = null; | |
leds = null; | |
readCounter = 0; | |
constructor() { | |
// Power save mode will reduce power consumption when the | |
// radio is idle. This adds latency when sending data. | |
imp.setpowersave(true); | |
initializeSensors(); | |
initializeLed(); | |
} | |
function run() { | |
// Set up the reading table with a timestamp | |
local reading = { "ts" : time() }; | |
// Add temperature and humidity readings | |
local result = tempHumid.read(); | |
if ("temperature" in result) reading.temperature <- result.temperature; | |
if ("humidity" in result) reading.humidity <- result.humidity; | |
// Check door status using internal LX sensor to | |
// determine if the door is open | |
reading.doorOpen <- (hardware.lightlevel() > LX_THRESHOLD); | |
local val = accel.getAccel(); | |
reading.accX <- val.x; | |
reading.accY <- val.y; | |
reading.accZ <- val.z; | |
server.log(format("Acceleration (G): (%0.2f, %0.2f, %0.2f)", val.x, val.y, val.z)); | |
local pressureReading = pressureSensor.read(); | |
if ("error" in pressureReading) { | |
server.error("An Error Occurred: " + pressureReading.err); | |
} else { | |
reading.pressure <- pressureReading.pressure; | |
reading.temp2 <- pressureReading.temperature; | |
// server.log(format("Current Pressure: %0.2f hPa, Current Temperature: %0.2f °C", | |
//pressureReading.pressure, pressureReading.temperature)); | |
} | |
if(readCounter == 0) { | |
// Send readings to the agent | |
agent.send("reading", reading); | |
} | |
readCounter++; | |
readCounter = (readCounter + 1) % 5; | |
leds.set(0, colorForAccel(val)).draw(); | |
// Schedule the next reading | |
imp.wakeup(READING_INTERVAL_SEC, run.bindenv(this)); | |
} | |
function colorForAccel(acc) { | |
local x = math.abs(acc.x * 100); | |
local y = math.abs(acc.y * 100); | |
local z = math.abs(acc.z * 100); | |
server.log(format("Acceleration (G): (x%0.2f, y%0.2f, z%0.2f)", x, y, z)); | |
if(x > y && x > z) { | |
return [floatToColorComponent(acc.x),0,0]; | |
} else if(y > x && y > z) { | |
return [0,floatToColorComponent(acc.y),0]; | |
} else if(z > x && z > y) { | |
return [0,0,floatToColorComponent(acc.z)]; | |
} | |
return [floatToColorComponent(acc.x),floatToColorComponent(acc.y),floatToColorComponent(acc.z)]; | |
} | |
function floatToColorComponent(f) { | |
// Expecting floats in the range -2.0f to 2.0f. | |
local fm = (f+2.0) * 63.000; | |
// Not in the range 0 to 255. | |
local r = fm.tointeger(); | |
//server.log(format("Converted: %0.2f to RGB %0.2f", f, r)); | |
return fm.tointeger(); | |
} | |
function initializeSensors() { | |
// Configure i2c | |
local i2c = hardware.i2c89; | |
i2c.configure(CLOCK_SPEED_400_KHZ); | |
// Initialize sensor | |
tempHumid = HTS221(i2c); | |
// <- class instances do not support the new slot operator | |
pressureSensor = LPS22HB(i2c); | |
// non-default I2C address for LIS2DH12 | |
accel = LIS3DH(i2c, 0x32); | |
// Configure sensor to take readings | |
tempHumid.setMode(HTS221_MODE.ONE_SHOT); | |
local rate = accel.setDataRate(50); | |
server.log(format("Accelerometer running at %dHz", rate)); | |
} | |
function initializeLed() { | |
hardware.pin1.configure(DIGITAL_OUT, 1); | |
//can be accessed via the imp001’s hardware.spi257 bus set to MSB_FIRST mode and a speed of 7500KB/s | |
hardware.spi257.configure(MSB_FIRST, 7500); | |
//Only imp001 pin 7 is actually used in this role | |
leds = WS2812(hardware.spi257, 1); | |
leds.set(0, [0,0,0]).draw(); | |
} | |
} | |
// RUNTIME | |
// --------------------------------------------------- | |
server.log("Device running..."); | |
// Initialize application | |
fridge <- SmartFridge(); | |
// Start reading loop | |
fridge.run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment