Last active
September 29, 2015 22:10
-
-
Save betzrhodes/600fd80d4272dacc5338 to your computer and use it in GitHub Desktop.
Basic Nora Example
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
#require "Firebase.class.nut:1.0.0" | |
///////// Application Code /////////// | |
// VARIABLES | |
agentID <- split(http.agenturl(), "/").pop(); | |
// INITIALIZE CLASS | |
const FIREBASENAME = "<--YOUR FIREBASE NAME-->"; | |
const FIREBASESECRET = "<--YOUR FIREBASE SECRET KEY-->"; | |
fb <- Firebase(FIREBASENAME, FIREBASESECRET); | |
// APPLICATION FUNCTIONS | |
// save settings to local storage | |
function saveSettings(settings) { | |
server.save({ "settings" : settings }); | |
} | |
// check local storage for settings and sync with device | |
function getSettings(dummy) { | |
local persist = server.load(); | |
// if no settings request from device | |
if (!("settings" in persist)) { device.send("getDeviceSettings", null); } | |
// if have settings send to device | |
if ("settings" in persist) { device.send("agentSettings", persist.settings); } | |
} | |
// overwrite default reading/reporting interval | |
// settings is a table | |
function updateSettings(settings) { | |
local persist = server.load(); | |
if ("settings" in persist) { | |
if ( !("readingInt" in settings) ) { | |
settings.readingInt <- persist.settings.readingInt; | |
} | |
if ( !("reportingInt" in settings) ) { | |
settings.reportingInt <- persist.settings.reportingInt; | |
} | |
} | |
saveSettings(settings); | |
device.send("agentSettings", settings); | |
} | |
// Store data to Firebase | |
function storeData(data) { | |
foreach(sensor, readings in data) { | |
server.log(sensor + " " + http.jsonencode(readings)); | |
buildQue(sensor, readings, writeQueToFB) | |
} | |
device.send("ack", "OK"); | |
} | |
// Sort readings by timestamp | |
function buildQue(sensor, readings, callback) { | |
readings.sort(function (a, b) { return b.ts <=> a.ts }); | |
callback(sensor, readings); | |
} | |
// Loop that writes readings to db in order by timestamp | |
function writeQueToFB(sensor, que) { | |
if (que.len() > 0) { | |
local reading = que.pop(); | |
fb.push("/data/"+agentID+"/"+sensor, reading, null, function(res) { writeQueToFB(sensor, que); }.bindenv(this)); | |
} | |
} | |
// DEVICE LISTENERS | |
device.on("deviceSettings", saveSettings); | |
device.on("getAgentSettings", getSettings); | |
device.on("data", storeData); | |
// // Uncomment if you want to update reading and/or reporting intervals | |
// local newSettings = {"reportingInt" : 600}; | |
// updateSettings(newSettings); |
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
// Temperature/Humidity | |
#require "Si702x.class.nut:1.0.0" | |
// Light/Proximity | |
#require "Si114x.class.nut:1.0.0" | |
// Air Pressure | |
#require "LPS25H.class.nut:2.0.0" | |
// Magnetometer | |
#require "LIS3MDL.class.nut:1.0.1" | |
// Temperature | |
#require "TMP1x2.class.nut:1.0.1" | |
// Accelerometer | |
#require "LIS3DH.class.nut:1.0.1" | |
// Custom built class to handle the sensors on Nora. | |
// This class does not have all the functionality for each sensor implemented. | |
// Current functionality includes: | |
// All sensors can take readings | |
// Temperature sensor interrupt | |
// Accelerometer sensor free fall interrupt | |
// You should modify this class to suit your specific needs. | |
class HardwareManager { | |
// 8-bit left-justified I2C address for sensors on Nora | |
// Temp | |
static TMP1x2_ADDR = 0x92; | |
// Temp/Humid | |
static Si702x_ADDR = 0x80; | |
// Amb Light | |
static Si1145_ADDR = 0xC0; | |
// Accelerometer | |
static LIS3DH_ADDR = 0x32; | |
// Air Pressure | |
static LPS25H_ADDR = 0xB8; | |
// Magnetometer | |
static LIS3MDL_ADDR = 0x3C; | |
// Wake Pins for sensors on Nora | |
static TEMP_PIN = hardware.pinE; | |
static AMBLIGHT_PIN = hardware.pinD; | |
static ACCEL_PIN = hardware.pinB; | |
static AIR_PRESS_PIN = hardware.pinA; | |
static MAG_PIN = hardware.pinC; | |
static ALERT_PIN = hardware.pin1; | |
// Wake Pin Polarity if Event is Triggered | |
static TEMP_EVENT = 0; | |
static AMBLIGHT_EVENT = 0; | |
static ACCEL_EVENT = 1; | |
static AIR_PRESS_EVENT = 1; | |
static MAG_EVENT = 0; | |
static ALERT_EVENT = 1; | |
// LED red pin4 | |
// LED green pin3 | |
// Variables to store initialized sensors | |
_temp = null; | |
_tempHumid = null; | |
_ambLight = null; | |
_accel = null; | |
_airPress = null; | |
_mag = null; | |
_i2c = null; | |
constructor() { | |
_configureI2C(); | |
_initializeSensors(); | |
_configureWakePins(); | |
} | |
/////////// Private Functions //////////// | |
function _configureI2C() { | |
_i2c = hardware.i2c89; | |
_i2c.configure(CLOCK_SPEED_400_KHZ); | |
} | |
function _initializeSensors() { | |
_temp = TMP1x2(_i2c, TMP1x2_ADDR); | |
_tempHumid = Si702x(_i2c, Si702x_ADDR); | |
_ambLight = Si114x(_i2c, Si1145_ADDR); | |
_accel = LIS3DH(_i2c, LIS3DH_ADDR); | |
_airPress = LPS25H(_i2c, LPS25H_ADDR); | |
_mag = LIS3MDL(_i2c, LIS3MDL_ADDR); | |
} | |
function _configureWakePins() { | |
AIR_PRESS_PIN.configure(DIGITAL_IN); | |
ACCEL_PIN.configure(DIGITAL_IN); | |
MAG_PIN.configure(DIGITAL_IN); | |
AMBLIGHT_PIN.configure(DIGITAL_IN); | |
TEMP_PIN.configure(DIGITAL_IN); | |
ALERT_PIN.configure(DIGITAL_IN_WAKEUP); | |
// disable unused interrupts | |
ambLightDisableInterrupt(); | |
pressureDisableInterrupt(); | |
// mag needs to be configured | |
// for wake pins on nora to work properly | |
_mag.configureInterrupt(true); | |
} | |
///////// Sleep & Wake Functions ////////// | |
function setLowPowerMode() { | |
// ambLight low power mode | |
_ambLight.enableALS(false); | |
_ambLight.enableProximity(false); | |
_ambLight.setDataRate(0); | |
// mag | |
_mag.enable(false); | |
} | |
function configureSensors() { | |
configureAccel(); | |
configurePressure(); | |
configureMagnetometer(); | |
configureTemp(); | |
} | |
//////// Temp sensor Functions //////// | |
function configureTemp() { | |
_temp.setActiveLow(); | |
_temp.setShutdown(0); | |
} | |
function tempRead(callback) { | |
_temp.read(function(result) { | |
if("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, {"temperature" : result.temp}); | |
} | |
}) | |
} | |
// opts format - {"mode" : "interrupt", "low" : 20, "high" : 30} | |
function tempConfigureInterrupt(opts) { | |
if ("mode" in opts) { | |
if (opts.mode == "comparator") { | |
_temp.setModeComparator(); | |
} | |
if (opts.mode == "interrupt") { | |
_temp.setModeInterrupt(); | |
} | |
} | |
if ("high" in opts) { | |
_temp.setHighThreshold(opts.high); | |
// server.log("Temp high threshold set: " + _temp.getHighThreshold()); | |
} | |
if ("low" in opts) { | |
_temp.setLowThreshold(opts.low); | |
// server.log("Temp low threshold set: " + _temp.getLowThreshold()); | |
} | |
} | |
///////// Temp/Humid Sensor Functions ///////// | |
function tempHumidRead(callback) { | |
_tempHumid.read(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, result); | |
} | |
}) | |
} | |
//////// Light/Proximity Sensor Functions /////// | |
function lightRead(callback) { | |
_ambLight.enableALS(true); | |
_ambLight.forceReadALS(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
// result table contains: visible, ir and uv | |
callback(null, result); | |
} | |
}); | |
} | |
function proximityRead(callback) { | |
_ambLight.enableProximity(true); | |
_ambLight.forceReadProximity(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, result) | |
} | |
}); | |
} | |
function ambLightDisableInterrupt() { | |
_ambLight.configureDataReadyInterrupt(false); | |
} | |
//////// Accelerometer Sensor Functions /////// | |
function configureAccel() { | |
_accel.init(); | |
_accel.enable(true); | |
_accel.setDataRate(50); | |
_accel.setLowPower(true); | |
} | |
function accelRead(callback) { | |
_accel.getAccel(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, {"accelerometer": result}); | |
} | |
}); | |
} | |
function configureAccelFreeFallInterrupt(state, threshhold = 0.5, duration = 15) { | |
_accel.configureInterruptLatching(true); | |
_accel.configureFreeFallInterrupt(state, threshhold, duration); | |
} | |
function getAccelInterruptTable() { | |
return _accel.getInterruptTable(); | |
} | |
///////// Pressure Sensor ////////// | |
function configurePressure() { | |
_airPress.softReset(); | |
_airPress.enable(true); | |
} | |
function pressureRead(callback) { | |
_airPress.read(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, result); | |
} | |
}); | |
} | |
function pressureDisableInterrupt() { | |
_airPress.configureInterrupt(false); | |
} | |
///////// Magnetometer Sensor ////////// | |
function configureMagnetometer() { | |
_mag.enable(true); | |
} | |
function magetometerRead(callback) { | |
_mag.readAxes(function(result) { | |
if ("err" in result) { | |
callback(result.err, null); | |
} else { | |
callback(null, {"magnetometer" : result}); | |
} | |
}); | |
} | |
} | |
// Custom built class to handle locaally stored data, | |
// wakeup, connection, and sending data. | |
// Constructor takes 2 parameters: | |
// 1st: reading interval - number of seconds between scheduled readings | |
// 2nd: reporting interval - number of seconds between scheduled | |
// connections to send data to the agent. | |
// You should modify this class to suit your specific needs. | |
class localDataManager { | |
readingInt = null; | |
reportingInt = null; | |
constructor(_reading, _reporting) { | |
readingInt = _reading; | |
reportingInt = _reporting; | |
_configureNV(); | |
} | |
function setReadingInt(newReadingInt) { | |
readingInt = newReadingInt; | |
} | |
function setReportingInt(newReportingInt) { | |
reportingInt = newReportingInt; | |
} | |
function getReadingInt() { | |
return readingInt; | |
} | |
function getReportingInt() { | |
return reportingInt; | |
} | |
function setNextWake() { | |
nv.nextWake <- (time() + readingInt); | |
} | |
function setNextConnect() { | |
nv.nextConnect <- (time() + reportingInt); | |
} | |
function readingTimerExpired() { | |
if (time() > nv.nextWake) { return true; } | |
return false | |
} | |
function reportingTimerExpired() { | |
if (time() > nv.nextConnect) { return true; } | |
return false | |
} | |
function storeData(sensorName, data) { | |
// add time stamp to data | |
data.ts <- time(); | |
// make sure sensor has a slot in nv | |
if (!(sensorName in nv.data)) { | |
nv.data[sensorName] <- []; | |
} | |
// add data to sensor's data array | |
nv.data[sensorName].push(data); | |
} | |
function sendData() { | |
agent.send("data", nv.data) | |
} | |
function clearNVReadings() { | |
nv.data <- {}; | |
} | |
function _configureNV() { | |
local root = getroottable(); | |
if ( !("nv" in root) ) { root.nv <- {}; } | |
if ( !("nextWake" in nv) ) { setNextWake(); } | |
if ( !("nextConnect" in nv) ) { setNextConnect(); } | |
if ( !("data" in nv) ) { nv.data <- {}; } | |
} | |
} | |
///////// Application Code /////////// | |
// VARIABLES | |
const DEFAULT_READING_INTERVAL = 60; | |
const DEFAULT_REPORTING_INTERVAL = 300; | |
const READINGS_TIMEOUT = 2; | |
const BLINKUP_TIMEOUT = 10; | |
const TEMP_THRESH_LOW = 26; | |
const TEMP_THRESH_HIGH = 29; | |
// INITIALIZE CLASSES | |
nora <- HardwareManager(); | |
ldm <- localDataManager(DEFAULT_READING_INTERVAL, DEFAULT_REPORTING_INTERVAL); | |
// APPLICATION FUNCTIONS | |
// Temperature and Accelerometer Interrupts | |
function setUpInterrupts() { | |
nora.tempConfigureInterrupt({"mode" : "interrupt", "low" : TEMP_THRESH_LOW, "high" : TEMP_THRESH_HIGH}); | |
nora.configureAccelFreeFallInterrupt(true); | |
} | |
// Set up sensors | |
function setUpSensors() { | |
nora.configureSensors(); | |
setUpInterrupts(); | |
} | |
// Take readings from each sensor and store in NV | |
function takeReadings() { | |
nora.tempRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("tempSensor", reading); | |
}); | |
nora.tempHumidRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("tempHumidSensor", reading); | |
}); | |
nora.lightRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("ambLightSensor", reading); | |
}); | |
nora.proximityRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("ambLightSensor", reading); | |
}); | |
nora.accelRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("accelerometerSensor", reading); | |
}); | |
nora.pressureRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("pressureSensor", reading); | |
}); | |
nora.magetometerRead(function(err, reading) { | |
if (err) { server.log(err); } | |
ldm.storeData("magnetometerSensor", reading); | |
}); | |
} | |
// Update next Wake and Connect times | |
function setTimers() { | |
ldm.setNextWake(); | |
ldm.setNextConnect(); | |
} | |
// Put Imp into Low power state | |
// then sleep until next scheduled Wake time | |
function sleep() { | |
local timer = nv.nextWake - time(); | |
nora.setLowPowerMode(); | |
// put imp to sleep | |
server.log("going to sleep for " + timer + " sec"); | |
if (server.isconnected()) { | |
imp.onidle(function() { server.sleepfor(timer); }); | |
} else { | |
imp.deepsleepfor(timer); | |
} | |
} | |
// Check if time to take readings and/or connect | |
// takes a parameter boolean value | |
// if true then sleep after checks | |
function checkTimers(ready) { | |
// check reading timer | |
if(ldm.readingTimerExpired()) { | |
takeReadings(); | |
ldm.setNextWake(); | |
// wait for readings, then check reporting timer | |
imp.wakeup(READINGS_TIMEOUT, function() { | |
// check reporting | |
if(ldm.reportingTimerExpired()) { | |
ldm.sendData(); | |
ldm.setNextConnect(); | |
} else { | |
if (ready) { sleep(); } | |
} | |
}); | |
} else { | |
if (ready) { sleep(); } | |
} | |
} | |
// Store reading and reporting interval on agent | |
function sendSettings(dummy) { | |
local settings = {"readingInt" : ldm.readingInt, "reportingInt" : ldm.reportingInt}; | |
agent.send("deviceSettings", settings); | |
} | |
// Update reading and/or reporting intervals and update timers | |
function updateSettings(settings) { | |
if ("readingInt" in settings) { ldm.setReadingInt(settings.readingInt); } | |
if ("reportingInt" in settings) { ldm.setReportingInt(settings.reportingInt); } | |
setTimers(); | |
} | |
// Check which event triggered and store event to local storage | |
function checkEvents() { | |
// If alert Pin triggered check for events | |
if (nora.ALERT_PIN.read() == nora.ALERT_EVENT) { | |
if (nora.TEMP_PIN.read() == nora.TEMP_EVENT) { | |
server.log("temp event fired"); | |
nora.tempRead(function(err, reading) { | |
if (err) { server.log(err); } | |
if (reading.temperature > TEMP_THRESH_LOW) { | |
ldm.storeData("tempSensor", {"event" : "Temp Too High"}); | |
} else { | |
ldm.storeData("tempSensor", {"event" : "Temp Too Low"}); | |
} | |
}); | |
} | |
if (nora.AMBLIGHT_PIN.read() == nora.AMBLIGHT_EVENT) { | |
// not configured, so just log if triggered | |
server.log("proximity event fired"); | |
} | |
if (nora.ACCEL_PIN.read() == nora.ACCEL_EVENT) { | |
server.log("accelerometer event fired"); | |
local data = nora.getAccelInterruptTable(); | |
if (data.int1) { | |
local event = { "event" : "Free Fall"}; | |
ldm.storeData("accelerometerSensor", event); | |
} | |
} | |
if (nora.AIR_PRESS_PIN.read() == nora.AIR_PRESS_EVENT) { | |
// not configured, so just log if triggered | |
server.log("air pressure event fired"); | |
} | |
if (nora.MAG_PIN.read() == nora.MAG_EVENT) { | |
// not configured, so just log if triggered | |
server.log("magnetometer event fired"); | |
} | |
} | |
// Events are cleared - Setup sensors | |
setUpSensors(); | |
// Check if time for reading | |
// Sleep or check events again in 5s | |
if (nora.ALERT_PIN.read() == nora.ALERT_EVENT) { | |
checkTimers(false); | |
imp.wakeup(5, function() { checkEvents(); }); | |
} else { | |
checkTimers(true); | |
} | |
} | |
// AGENT LISTENERS | |
agent.on("agentSettings", updateSettings); | |
agent.on("getDeviceSettings", sendSettings); | |
agent.on("ack", function(res) { | |
ldm.clearNVReadings(); | |
sleep(); | |
}); | |
// WAKEUP LOGIC | |
switch(hardware.wakereason()) { | |
case WAKEREASON_TIMER: | |
server.log("WOKE UP B/C TIMER EXPIRED"); | |
setUpSensors(); | |
checkTimers(true); | |
break; | |
case WAKEREASON_PIN: | |
server.log("WOKE UP B/C ALERT PIN HIGH"); | |
checkEvents(); | |
break; | |
case WAKEREASON_POWER_ON: | |
server.log("COLD BOOT"); | |
setUpSensors(); | |
agent.send("getAgentSettings", null); | |
takeReadings(); | |
// Wait reasonable time for a blink up before going to sleep | |
imp.wakeup(BLINKUP_TIMEOUT, sleep); | |
break; | |
default: | |
server.log("WOKE UP B/C RESTARTED DEVICE, LOADED NEW CODE, ETC"); | |
setUpSensors(); | |
agent.send("getAgentSettings", null); | |
takeReadings(); | |
// Wait for readings then sleep | |
imp.wakeup(READINGS_TIMEOUT, sleep); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment