Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active October 16, 2017 10:49
Show Gist options
  • Save ElectricImpSampleCode/3aff6e05949c3c71c280e1ecd32d8313 to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/3aff6e05949c3c71c280e1ecd32d8313 to your computer and use it in GitHub Desktop.
BPSN Fridge Monitor Example
// 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");
}
}
});
// 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