Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active November 25, 2019 13:43
Show Gist options
  • Save ElectricImpSampleCode/f1c9353fb921ea7fc9c64002f145237f to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/f1c9353fb921ea7fc9c64002f145237f to your computer and use it in GitHub Desktop.
impExplorer Sample Code: Motion Detector
// Copyright (c) 2018, Electric Imp, Inc.
// Licence: MIT
// IMPORTS
#require "rocky.class.nut:2.0.2"
// CONSTANTS
const DATA_PER_SECOND = 50.0;
const TWO_PI = 6.283185307179586;
const HTML_DATA = @"
<html>
<head>
<script type='text/javascript'src='https://www.google.com/jsapi'></script>
<script type='text/javascript' src='https://code.jquery.com/jquery-latest.js'></script>
<script type='text/javascript'>
google.load('visualization', '1', {packages: ['corechart', 'controls']});
google.setOnLoadCallback(function() { drawVisualization('[]'); });
var chart;
function drawVisualization(chartData) {
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));
var control = new google.visualization.ControlWrapper({
'controlType': 'ChartRangeFilter',
'containerId': 'control',
'options': {
// Filter by the date axis.
'filterColumnIndex': 0,
'ui': { 'chartType': 'LineChart',
'chartOptions': { 'chartArea': {'width': '90%'},
'hAxis': {'baselineColor': 'none'} },
// Display a single series that shows the closing value of the stock.
// Thus, this view has two columns: the date (axis) and the stock value (line series).
'chartView': { 'columns': [0, 1]},
// 1 day in milliseconds = 24 * 60 * 60 * 1000 = 86,400,000
'minRangeSize': 86400000 } },
});
chart = new google.visualization.ChartWrapper({
'chartType': 'LineChart',
'containerId': 'chart',
'options': {
// Use the same chart area width as the control for axis alignment.
'chartArea': {'height': '80%', 'width': '90%'},
'hAxis': {'slantedText': false, 'minorGridlines':{'count':'1'}},
'vAxis': {'minorGridlines':{'count':'5'}},
'legend': {'position': 'none'}},
});
var data = new google.visualization.DataTable();
//columns
data.addColumn('number','Time');
data.addColumn('number','Level');
//rows
data.removeRows(0,data.getNumberOfRows());
data.addRows(JSON.parse(chartData));
dashboard.bind(control, chart);
dashboard.draw(data);
}
function record(){
$.ajax({
type:'POST',
url: window.location +'/start',
data: '',
success: drawVisualization,
timeout: 120000,
error: (function(err){
console.log(err);
console.log('Error parsing device info from imp');
return;
})
});
}
function download(){
console.log('Download it!');
var uri = chart.getChart().getImageURI();
var fn = document.getElementById('filename').value;
var ld = document.getElementById('link_div');
ld.innerHTML = '<a href='+uri+' id=""link_a"" download='+fn+'></a>';
var la = document.getElementById('link_a');
var clickEvent = document.createEvent('MouseEvent');
clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
document.getElementById('link_a').dispatchEvent(clickEvent);
}
var interval = null;
function start(){
document.getElementById('start_mode').disabled = true;
document.getElementById('wakeup_mode').disabled = false;
interval = setInterval(function() {
record();
}, 5000);
}
function wakeup(){
document.getElementById('start_mode').disabled = false;
document.getElementById('wakeup_mode').disabled = true;
$.ajax({
type:'POST',
url: window.location +'/wakeup',
data: '',
success: drawVisualization,
timeout: 120000,
error: (function(err){
console.log(err);
console.log('Error parsing device info from imp');
return;
})
});
}
</script>
</head>
<body>
<button type='button' id='start_mode' onclick='start()'>Continuous Mode</button>
<button type='button' id='wakeup_mode' onclick='wakeup()'>Wakeup Mode</button>
<div id='dashboard' style='width: 100%%'>
<div id='chart' style='width: 99%%; height: 400px;'></div>
<div id='control' style='width: 99%%; height: 50px;'></div>
</div>
<p><p>
<div id='download_div'>
<input type='button' value='Download' onclick='download();'>
<input type='text' id='filename' value='impact_test'>
</div>
<div id='link_div'></div>
</body>
</html>";
// GLOBALS
savedContext <- null; // Web app request context
savedValues <- null; // collected accelerometer data
// RUNTIME
// Set up the handler function for data received from the device
device.on("accelData", function(values) {
savedValues = [[0, 0]];
savedValues.clear();
// Fast Fourier Transform the incoming data
local len = values.len() / 4;
// Prepare a frequency domain visualization
local result = blob(len);
local arrReal = array(len,0.0);
local arrImag = array(len,0.0);
local invlen = 1.0 / len;
for (local i = 0 ; i < len ; i++) {
values.seek(0);
for (local n = 0 ; n < len ; n++) {
local theta = TWO_PI * i * n * invlen;
local costheta = math.cos(theta);
local sintheta = math.sin(theta);
local valueMag = values.readn('f');
arrReal[i] += valueMag * costheta;
arrImag[i] += valueMag * sintheta;
}
arrReal[i] *= invlen;
arrImag[i] *= invlen;
savedValues.append([i + 1, math.sqrt(arrReal[i] * arrReal[i] + arrImag[i] * arrImag[i])]);
}
server.log("Graphing the accelerometer data");
// send the decoded data along to the web app to be graphed
imp.wakeup(0, function() {
savedContext.send(200, http.jsonencode(savedValues));
});
});
// Set up the agent's API
api <- Rocky({"timeout": 64000});
// Serve the web UI to any GET request to the root agent URL
api.get("/", function(context) {
context.send(200, HTML_DATA);
});
// Start collecting data when requested by the web app, ie.
// the user clicks the 'Continuous Mode' button
api.post("/start", function(context) {
server.log("Collecting accelerometer sample");
// Send the 'start' message to the device to request data
device.send("start", null);
// hold the request context so that data can be sent back
// to the web UI when the device has collected data
savedContext = context;
});
// Start collecting data when requested by the web app, ie.
// the user clicks the 'Wakeup Mode' button
api.post("/wakeup", function(context) {
server.log("Entering Wakeup mode");
// Send the 'wakeup' message to the device to put into the correct mode
device.send("wakeup", null);
// Hold the request context so that BlinkUp data can be sent back
// to this web UI when the device has collected data
savedContext = context;
});
// Copyright (c) 2018, Electric Imp, Inc.
// Licence: MIT
// IMPORTS
#require "LIS3DH.device.lib.nut:2.0.1"
// CONSTANTS
const LENGTH_OF_ACQUISITION = 2.56;
const DATA_PER_SECOND = 100;
// 'GLOBALS'
local totalAquiredData = 4 * LENGTH_OF_ACQUISITION * DATA_PER_SECOND;
local accelerometerData = blob(totalAquiredData);
local wakeupMode = false;
wakePin <- hardware.pin1;
i2c <- hardware.i2c89;
// Configure the I2C bus and the acceleromter connected to it
i2c.configure(CLOCK_SPEED_400_KHZ);
accelerometer <- LIS3DH(i2c, 0x32);
accelerometer.setDataRate(DATA_PER_SECOND);
accelerometer.setRange(8);
// FUNCTIONS
function readBuffer() {
// Read a block of data from the accelerometer's FIFO buffer
if (wakePin.read() == 0) return;
// Read the buffer
local stats = accelerometer.getFifoStats();
// Run through the received FIFO data and package the data values
for (local i = 0 ; i < stats.unread ; i++) {
local data = accelerometer.getAccel();
accelerometerData.writen(data.x, 'f');
if (accelerometerData.eos() != null) {
// Send the current batch of readings to the agent for graphing
agent.send("accelData", accelerometerData);
//i = stats.unread;
// If we are in wakeup mode, put the impExplorer back to sleep
// and awaiting the next wake interrupt
if (wakeupMode) {
// Configure pin 1 to wake the impExplorer
hardware.pin1.configure(DIGITAL_IN_WAKEUP);
// Configure the accelerometer to signal pin 1 upon detecting movement
accelerometer.configureClickInterrupt(true, LIS3DH_SINGLE_CLICK);
accelerometer.configureFifoInterrupt(false, LIS3DH_FIFO_BYPASS_MODE);
// Put the impExplorer to sleep as soon as it (ie. impOS) goes idle
imp.onidle(function() {
server.log("impExlorer has nothing to do, so is going to sleep for 30 mins");
server.sleepfor(1800);
});
} else {
// We are in continuous reading mode, so don't sleep,
// just wait for a subsequent Start command
accelerometer.configureFifoInterrupt(false, LIS3DH_FIFO_BYPASS_MODE);
}
break;
}
}
}
// Set up command handlers by registering the functions that will be called
// upon receipt of the specified messages from the agent (they are triggered
// in response to button clicks in the agent-served browser UI)
// Normal data acquisition mode: collect accelerometer data and
// send it to the agent periodically for graphing
agent.on("start", function(dummy) {
// Put the data store (blob) pointer back to the start of the store
accelerometerData.seek(0);
// Configure the accelerometer's FIFO buffer in Stream Mode and set the interrupt generator
accelerometer.configureFifoInterrupt(true, LIS3DH_FIFO_STREAM_MODE, 30);
// Configure the impExplorer's interrupt pin, which will be triggered by the accelerometer.
// In turn, this will trigger a call to the function 'readBuffer()'
wakePin.configure(DIGITAL_IN_PULLDOWN, readBuffer);
wakeupMode = false;
});
// Set the impExplorer to wakeup mode, ie. stop regular data collection and instead
// put the impExplorer to sleep to wait to be woken by the accelerometer
agent.on("wakeup", function(dummy) {
// Configure pin 1 to wake the impExplorer
wakePin.configure(DIGITAL_IN_WAKEUP);
// Configure the accelerometer to signal pin 1 upon detecting movement
accelerometer.configureClickInterrupt(true, LIS3DH_SINGLE_CLICK);
accelerometer.configureFifoInterrupt(false, LIS3DH_FIFO_BYPASS_MODE);
// Put the impExplorer to sleep as soon as it (ie. impOS) goes idle
imp.onidle(function() {
server.log("impExlorer has nothing to do, so is going to sleep for 30 mins");
server.sleepfor(1800);
});
});
// In the final section of the code, we check if the impExplorer was woken by an
// assertion of the wakeup pin. If it was - as configured by a request for wakeup mode
// (see above) - then begin collecting accelerometer data to graph
if (WAKEREASON_PIN == hardware.wakereason()) {
// Put the data store (blob) pointer back to the start of the store
accelerometerData.seek(0);
// Configure the FIFO buffer in Stream Mode and set the interrupt generator
accelerometer.configureClickInterrupt(false, LIS3DH_SINGLE_CLICK);
accelerometer.configureFifoInterrupt(true, LIS3DH_FIFO_STREAM_MODE, 30);
// Configure the impExplorer's interrupt pin, which will be triggered by the accelerometer.
// In turn, this will trigger a call to the function 'readBuffer()'
wakePin.configure(DIGITAL_IN_PULLDOWN, readBuffer);
wakeupMode = true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment