Last active
November 25, 2019 13:43
-
-
Save ElectricImpSampleCode/f1c9353fb921ea7fc9c64002f145237f to your computer and use it in GitHub Desktop.
impExplorer Sample Code: Motion Detector
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
// 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; | |
}); |
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
// 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