Last active
October 16, 2017 10:49
-
-
Save ElectricImpSampleCode/3aff6e05949c3c71c280e1ecd32d8313 to your computer and use it in GitHub Desktop.
BPSN Fridge Monitor 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
// Set up a table of static calendar functions | |
utilities <- {}; | |
utilities.dayOfWeek <- function(d, m, y) { | |
local dim = [ | |
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], | |
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] | |
]; | |
local ad = ((y - 1) * 365) + _totalLeapDays(y) + d - 5; | |
for (local i = 0 ; i < m ; ++i) { | |
local a = dim[utilities._isLeapYear(y)]; | |
ad = ad + a[i]; | |
} | |
return (ad % 7) - 1; | |
} | |
utilities.isLeapYear <- function(y) { | |
if (utilities._isLeapYear(y) == 1) return true; | |
return false; | |
} | |
utilities.bstCheck <- function() { | |
// Checks the current date for British Summer Time, | |
// returning true or false accordingly | |
local n = date(); | |
if (n.month > 2 && n.month < 9) return true; | |
if (n.month == 2) { | |
// BST starts on the last Sunday of March | |
for (local i = 31 ; i > 24 ; --i) { | |
if (utilities.dayOfWeek(i, 2, n.year) == 0 && n.day >= i) return true; | |
} | |
} | |
if (n.month == 9) { | |
// BST ends on the last Sunday of October | |
for (local i = 31 ; i > 24 ; --i) { | |
if (utilities.dayOfWeek(i, 9, n.year) == 0 && n.day < i) return true; | |
} | |
} | |
return false; | |
} | |
utilities.dstCheck <- function() { | |
// Checks the current date for US Daylight Savings Time, | |
// returning true or false accordingly | |
local n = date(); | |
if (n.month > 2 && n.month < 10) return true; | |
if (n.month == 2) { | |
// DST starts second Sunday in March | |
for (local i = 8 ; i < 15 ; ++i) { | |
if (utilities.dayOfWeek(i, 2, n.year) == 0 && n.day >= i) return true; | |
} | |
} | |
if (n.month == 10) { | |
// DST ends first Sunday in November | |
for (local i = 1 ; i < 8 ; ++i) { | |
if (utilities.dayOfWeek(i, 10, n.year) == 0 && n.day <= i) return true; | |
} | |
} | |
return false; | |
} | |
utilities._totalLeapDays <- function(y) { | |
local t = y / 4; | |
if (utilities._isLeapYear(y) == 1) t = t - 1; | |
t = t - ((y / 100) - (1752 / 100)) + ((y / 400) - (1752 / 400)); | |
return t; | |
} | |
utilities._isLeapYear <- function(y) { | |
if ((y % 400) || ((y % 100) && !(y % 4))) return 1; | |
return 0; | |
} | |
utilities.timestampToIso <- function(timestamp) { | |
// This function cheats on the addition of an hour to the readout | |
// for daylight savings time (ie. it is not responsive to real timezones) | |
// It also works to UK daylight saving time, so | |
// use utilities.dstCheck() for use in the US | |
local now = date(timestamp); | |
local ts = "+00:00"; | |
if (utilities.bstCheck()) { | |
now.hour++; | |
if (now.hour > 23) now.hour = 0; | |
ts = "+01:00"; | |
} | |
return format("%i-%02i-%02i %02i:%02i:%02i %s", now.year, now.month + 1, now.day, now.hour, now.min, now.sec, ts); | |
} | |
// RUNTIME | |
// Register a handler for 'door open too long' alerts | |
device.on("fridge.door.alert", function(data) { | |
// For now, just dump the alert to the log | |
server.log("[WARNING] " + data.message + " (triggered at " + timestampToIso(data.timestamp) + ")"); | |
}); | |
// Register a handler for 'data upload on door opening' messages | |
device.on("fridge.data.upload", function(data) { | |
// For now, just dump the data to the log | |
if ("timestamp" in data) server.log ("Data posted by device at " + utilities.timestampToIso(data.timestamp)); | |
if ("openduration" in data) server.log (format("Door closed after %i seconds", data.openduration)); | |
if ("connecttime" in data) server.log (format("Connected to server in %i seconds", data.connecttime)); | |
if ("lightlevel" in data) { | |
local pc = (data.lightlevel.tofloat() / 65535.0) * 100.0; | |
server.log ("Light level with door open: " + data.lightlevel + format(" (%.1f%%)", pc)); | |
} | |
if ("data" in data) { | |
local dataPoints = data.data; | |
if (dataPoints.len() > 0) { | |
foreach (dataPoint in dataPoints) { | |
local timeString = utilities.timestampToIso(dataPoint[0]); | |
if (dataPoint[1].len() > 0) { | |
server.log("Error at " + timeString + ": " + dataPoint[1]); | |
} else { | |
server.log("Datapoint at " + timeString + ": " + format("temperature %0.2f°C, humidity %0.2f%s", dataPoint[2], dataPoint[3], "%")); | |
} | |
// Pause a second between log entries | |
imp.sleep(1.0); | |
} | |
} else { | |
server.log("But no readings were included"); | |
} | |
} | |
}); |
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
// IMPORTS | |
// Temperature Humidity sensor Library | |
#require "HTS221.device.lib.nut:2.0.1" | |
// EARLY-FIRE CODE | |
// Drop into low-power WiFi mode | |
imp.setpowersave(true); | |
// Set the timeout policy to advanced model | |
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_FOR_ACK, 10); | |
// CONSTANTS | |
const OPEN_LIGHT_LEVEL = 30000; | |
const DOOR_CHECK_TIME = 0.25; | |
const READING_TIME = 300; | |
const DOOR_OPEN_LIMIT = 20; | |
// GLOBALS | |
local thermalLoopTimer = null; | |
local data = null; | |
local tempSensor = null; | |
local connectedFlag = true; | |
local connectingFlag = false; | |
local getDataCount = 0; | |
local openTime = 0; | |
local openCount = 0; | |
// Sensor Node HAL | |
sensorNodeHAL <- { | |
"LED_BLUE" : hardware.pinP, | |
"LED_GREEN" : hardware.pinU, | |
"SENSOR_I2C" : hardware.i2cAB, | |
"TEMP_HUMID_I2C_ADDR" : 0xBE, | |
"ACCEL_I2C_ADDR" : 0x32, | |
"PRESSURE_I2C_ADDR" : 0xB8, | |
"RJ12_ENABLE_PIN" : hardware.pinS, | |
"ONEWIRE_BUS_UART" : hardware.uartDM, | |
"RJ12_I2C" : hardware.i2cFG, | |
"RJ12_UART" : hardware.uartFG, | |
"WAKE_PIN" : hardware.pinW, | |
"ACCEL_INT_PIN" : hardware.pinT, | |
"PRESSURE_INT_PIN" : hardware.pinX, | |
"TEMP_HUMID_INT_PIN" : hardware.pinE, | |
"NTC_ENABLE_PIN" : hardware.pinK, | |
"THERMISTER_PIN" : hardware.pinJ, | |
"FTDI_UART" : hardware.uartQRPW, | |
"PWR_3v3_EN" : hardware.pinY | |
} | |
// LOOP MANAGEMENT FUNCTIONS | |
function doorSensorLoop() { | |
// Queue up the next iteration of the door-sensing loop | |
imp.wakeup(DOOR_CHECK_TIME, doorSensorLoop); | |
// Is the door open? Check the light level to find out | |
local lightLevel = hardware.lightlevel(); | |
// If the measured light level is higher than the set threshold, | |
// we try to connect, otherwise swe disconnect (door is closed) | |
if (lightLevel > OPEN_LIGHT_LEVEL) { | |
connect(); | |
} else { | |
disconnect(); | |
} | |
} | |
function thermalSensorLoop() { | |
// Secondary loop to repeatedly read the temperature/humidity sensor | |
thermalLoopTimer = imp.wakeup(READING_TIME, thermalSensorLoop); | |
// Read and store sensor data every 30s if we are disconnected | |
if (!connectedFlag) getData(); | |
} | |
// CONNECTION MANAGEMENT FUNCTIONS | |
function connect() { | |
// Are we connected already? If so, bail | |
if (connectedFlag) return; | |
if (!connectingFlag) { | |
// We are not currently trying to connect, so we do so now | |
connectingFlag = true; | |
if (openTime == 0) { | |
// Start the open door timer | |
openTime = time(); | |
// Set the timer on the door alert | |
openCount = 1; | |
imp.wakeup(DOOR_OPEN_LIMIT, doorAlert); | |
} | |
// Attempt to connect with a 10-second timeout (need to be quick) | |
server.connect(connected, 10); | |
} | |
} | |
function connected(reason) { | |
// This function manages the result of attempting to connect | |
// and unexpected disconnections (which should be rare since we're almost | |
// never connected to the server) | |
connectingFlag = false; | |
if (reason != SERVER_CONNECTED) { | |
// Connection attempt timed out or failed for some reason | |
connectedFlag = false; | |
} else { | |
// We have successfully connected | |
connectedFlag = true; | |
// Process any stored data we have | |
processData(); | |
} | |
} | |
function disconnect() { | |
// Disconnect from the server only if we're connected | |
if (!connectedFlag) return; | |
// Send one last message, indicating how long the door was open | |
local data = {}; | |
data.timestamp <- time(); | |
if (openTime != 0) data.openduration <- (data.timestamp - openTime); | |
openTime = 0; | |
agent.send("fridge.data.upload", data); | |
// Now disconnect | |
connectedFlag = false; | |
server.disconnect(); | |
} | |
// DATA MANAGEMENT FUNCTIONS | |
function processData() { | |
// Upload the stored data, but only if there is some | |
if (data.data.len() > 0) { | |
// Temporarily halt the thermal sensor loop while we transfer data | |
if (thermalLoopTimer != null) imp.cancelwakeup(thermalLoopTimer); | |
thermalLoopTimer = null; | |
// Add a timestamp to the data to be transferred... | |
data.timestamp <- time(); | |
// ...and the registered light level... | |
data.lightlevel <- hardware.lightlevel(); | |
// ...and the length of time to connect | |
if (openTime != 0) data.connecttime <- (data.timestamp - openTime); | |
// Transfer the data | |
agent.send("fridge.data.upload", data); | |
// Clear the data table | |
resetData(); | |
// Restart the thermal sensor loop | |
thermalSensorLoop(); | |
} | |
} | |
function getData() { | |
// Get a temperature and humidity reading from the sensor | |
// Typically this happens every 300s | |
tempSensor.read(function(result) { | |
// Each data point is an array with the following fixed-place elements: | |
// 1. Timestamp | |
// 2. Error message, or empty string | |
// 3. Temperature reading, or null | |
// 4. Humidity reading, or null | |
local dataPoint = []; | |
dataPoint.append(time()); | |
if ("error" in result) { | |
// Store the error | |
dataPoint.append(result.error); | |
} else { | |
dataPoint.append(""); | |
dataPoint.append(result.temperature); | |
dataPoint.append(result.humidity); | |
} | |
data.data.append(dataPoint); | |
}.bindenv(this)); | |
} | |
function resetData() { | |
// Clear and prepare the data table | |
data = {}; | |
data.data <- []; | |
getDataCount = 0; | |
} | |
function doorAlert() { | |
if (connectedFlag) { | |
// We can only issue a warning if we are connected | |
local data = {}; | |
data.timestamp <- time(); | |
data.message <- format("[ALERT] Fridge door open for at least %i seconds", DOOR_OPEN_LIMIT * openCount); | |
agent.send("fridge.door.alert", data); | |
// Set the timer on the door alert | |
openCount += 1; | |
imp.wakeup(DOOR_OPEN_LIMIT, doorAlert); | |
} | |
} | |
// RUNTIME | |
// NOTE we start off connected so we have to disconnect after posting the log message | |
server.log("BPSN starting, disconnecting..."); | |
server.disconnect(); | |
connectedFlag = false; | |
// Handle unexpected disconnections (see the target function) | |
server.onunexpecteddisconnect(connected); | |
// Set up the data store | |
resetData(); | |
// Set up the sensor | |
sensorNodeHAL.SENSOR_I2C.configure(CLOCK_SPEED_400_KHZ); | |
tempSensor = HTS221(sensorNodeHAL.SENSOR_I2C, sensorNodeHAL.TEMP_HUMID_I2C_ADDR); | |
tempSensor.setMode(HTS221_MODE.ONE_SHOT); | |
// Start the sensor loops | |
imp.wakeup(0.1, function() { | |
doorSensorLoop(); | |
thermalSensorLoop(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment