Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FishOfPrey/46a21880092754d3ca7f5935eb2f017e to your computer and use it in GitHub Desktop.
Save FishOfPrey/46a21880092754d3ca7f5935eb2f017e to your computer and use it in GitHub Desktop.
Electric Imp SmartFridge
// 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);
// 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