Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Created October 3, 2017 15:18
Show Gist options
  • Save ElectricImpSampleCode/53f88292b8da09670e5f3e4c724b4adf to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/53f88292b8da09670e5f3e4c724b4adf to your computer and use it in GitHub Desktop.
Factory firmware with agent-hosted WiFi credentials UI
// Import Electric Imp's REST API management library
#require "Rocky.class.nut:2.0.0"
// Replace the following two strings with your server logging endpoint
const RESULTS_URL = "<YOUR_RESULT_LOGGING_URL>";
// Define the web page HTML as a constant
const HTML_STRING = @"<!DOCTYPE html><html lang='en-US'><meta charset='UTF-8'>
<html>
<head>
<title>Factory BlinkUp Fixture</title>
<link rel='stylesheet' href='https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css'>
<link href='https://fonts.googleapis.com/css?family=Abel' rel='stylesheet'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<style>
.center { margin-left: auto; margin-right: auto; margin-bottom: auto; margin-top: auto; }
body {background-color: black}
p {color: white; font-family: Abel, sans-serif; font-size: 16px}
p.colophon {font-family: Abel, sans-serif; font-size: 13px}
p.input {color: black}
h2 {color: white; font-family: Abel, sans-serif; font-weight:bold}
td {color: white; font-family: Abel, sans-serif}
</style>
</head>
<body>
<div class='container' style='padding: 40px'>
<div style='border:3px solid white;background-color:#888888'>
<h2 class='name-status' align='center'>Electric Imp Factory BlinkUp&trade; Fixture</h2>
<p class='error-message' align='center'>&nbsp;<i><span></span></i>&nbsp;</p>
<div class='controls-area' align='center'>
<p align='center'>Enter the factory WiFi settings in the SSID and password fields below<br>
These settings will be used to configure Devices Under Test (DUTs) for Internet access<br>&nbsp;</p>
<div class='update-button'>
<p>Factory WiFi SSID&nbsp;&nbsp;<input id='issid' style='color:black' type='text'></input></p>
<p>Factory WiFi Password&nbsp;&nbsp;<input id='ipwd' style='color:black' type='password'></input><br>&nbsp;
<input type='checkbox' name='showpw' id='showpw'> Show Password<br>&nbsp;</p>
<button style='color:dimGrey;font-family:Abel,sans-serif;font-size: 16pt;width:280px;height:36px' id='fixture-button'>Set Factory WiFi Settings</button><br>&nbsp;
</div>
</div>
<p class='colophon' align='center'>&nbsp;<br>Copyright &copy; Electric Imp, 2017</p>
</div>
</div>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
<script>
var agenturl = '%s';
getState(updateReadout);
$('.update-button button').click(setFixtureWiFi);
$('#showpw').click(togglePasswordField);
function updateReadout(undata) {
data = JSON.parse(undata);
document.getElementById('issid').value = (data.ssid == '' ? 'Unknown' : data.ssid);
document.getElementById('ipwd').value = (data.pwd == '' ? 'Unknown' : data.pwd);
}
function togglePasswordField() {
try {
var pwf = document.getElementById('ipwd');
pwf.type = ((document.getElementById('showpw').checked == true) ? 'text' : 'password');
} catch (err) {
// Doesn't work? Disable the button for old browsers
$('#showpw').click(null);
$('#showpw').prop('disabled', true);
}
}
function getState(callback) {
$.ajax({
url : agenturl + '/state',
type: 'GET',
success : function(response) {
if (callback) {
callback(response);
}
}
});
}
function setFixtureWiFi() {
var s = document.getElementById('issid').value;
var p = document.getElementById('ipwd').value;
if (s == '' || s == 'Unknown' || p == 'Unknown') {
$('.error-message span').text('Please enter a valid SSID/password combintion');
return;
}
$.ajax({
url : agenturl + '/fixture',
type: 'POST',
data: JSON.stringify({ 'ssid' : s,
'pwd' : p }),
success : function(response) {
$('.error-message span').text('WiFi settings applied');
getState(updateReadout);
}
});
}
</script>
</body>
</html>";
// Declare global variables
blinkupCount <- 0;
assid <- "";
apwd <- "";
api <- Rocky();
// Set up an receiver for "testresult" messages from the device under test
// This will not be called by a factory BlinkUp fixture, because our device-side
// firmare only sends this message from production devices
device.on("testresult", function(data) {
local headers = { "Content-Type": "application/json" };
local body = http.jsonencode(data);
local deviceid = imp.configparams.deviceid;
// The following line is helpful for debugging factory firmware during development.
server.log(format("Posting 'test results' for device %s: %s", deviceid, body));
// Send the test result data to your server
http.post(RESULTS_URL, headers, body).sendasync(function(response) {
if (response.statuscode >= 300) {
// Again, the following line will not be called in a production environment
server.error(format("Failed posting test results for device %s with response %d:%s", deviceid, response.statuscode, response.body));
}
});
});
// Set up a receiver for "blinkupsent" messages from the factory fixture
// This will not be called by a production device under test, because our device-side
// firmare only sends this message from factory fixtures
device.on("blinkupsent", function(value) {
// Increment the BlinkUp tally
++blinkupCount;
// Prepare the notification
local headers = { "Content-Type": "application/json" };
local data = {};
data.fixture_url <- FactoryTools.getFactoryFixtureURL();
data.blinkup_count <- blinkupCount;
data.timestamp <- time();
data.message <- format("BlinkUp %i triggered by this fixture", blinkupCount);
local body = http.jsonencode(data);
// Send the notification to your server
http.post(RESULTS_URL, headers, body).sendasync(function(response) {
if (response.statuscode >= 300) {
// Again, the following line will not be called in a production environment
server.error(format("Failed posting blinkup notification for device %s with response %d:%s", FactoryTools.getFactoryFixtureURL(), response.statuscode, response.body));
}
});
});
// Register handler for WiFi settings requests
// NOTE DUT will never call this, only the fixture
device.on("fixture.get.wifi.credentials", function(dummy) {
if (assid != "") {
local data = {};
data.ssid <- assid;
data.pwd <- apwd;
device.send("fixture.set.wifi.credentials", data);
}
});
// Establish the agent REST API
api.get("/", function(context) {
// Root request: just return the UI HTML
context.send(200, format(HTML_STRING, http.agenturl()));
});
api.get("/state", function(context) {
// Return the current ssid, password combination
context.send(200, http.jsonencode({ "ssid": assid, "pwd": apwd }));
});
api.post("/fixture", function(context) {
// Set the WiFi data from the web page
local data
try {
data = http.jsondecode(context.req.rawbody);
} catch (err) {
server.error(err);
context.send(400, "Bad data posted");
return;
}
if ("ssid" in data) assid = data.ssid;
if ("pwd" in data) apwd = data.pwd;
// Send the new credentials to the fixture
device.send("fixture.set.wifi.credentials", { "ssid": assid, "pwd": apwd });
context.send(202, "OK");
});
// Copyright (c) 2015-17 Electric Imp
// This program is licensed under the MIT License (http://opensource.org/licenses/MIT)
// Load the Factory Tools Library, which simplifies detecting Fixture or DUT
#require "FactoryTools.class.nut:2.1.0"
// Load the CFAx33KL Library, which abstracts the LCD/Keypad used on the impFactory
#require "CFAx33KL.class.nut:1.1.0"
// CONSTANTS
// This sets how long to wait (seconds) after triggering BlinkUp before allowing another
const BLINKUP_TIME = 10;
// Flag used to prevent new BlinkUp triggers while BlinkUp is running
sendingBlinkUp <- false;
// WiFi credentials to send to the DUT
ssid <- "";
pwd <- "";
// FACTORY FIXTURE FUNCTIONS
function configureBlinkUpTrigger(pin) {
// Register a state-change callback for BlinkUp Trigger Pins
pin.configure(DIGITAL_IN, function() {
// Trigger only on rising edges, when BlinkUp is not already running
// and we have an SSID
if (pin.read() && !sendingBlinkUp && ssid != "") {
sendingBlinkUp = true;
imp.wakeup(BLINKUP_TIME, function() {
sendingBlinkUp = false;
});
// Send factory BlinkUp
server.factoryblinkup(ssid, pwd, blinkupPin, BLINKUP_FAST);
// Signal the agent that BlinkUp has been performed
agent.send("blinkupsent", true);
}
});
}
function configureFactoryFixture() {
// Assign pins
greenBtn <- hardware.pinC;
ledGreen <- hardware.pinE;
ledRed <- hardware.pinF;
footSwitch <- hardware.pinH;
blinkupPin <- hardware.pinM;
// Initialize front panel LEDs to Off
ledRed.configure(DIGITAL_OUT, 0);
ledGreen.configure(DIGITAL_OUT, 0);
// Initiate factory BlinkUp on either a front-panel button or footswitch press
configureBlinkUpTrigger(greenBtn);
configureBlinkUpTrigger(footSwitch);
// Print a message on the LCD and store it as default
lcd <- CFAx33KL(hardware.uart2);
lcd.clearAll();
lcd.setLine1("Electric Imp");
lcd.setLine2("impFactory");
lcd.setBrightness(100);
lcd.storeCurrentStateAsBootState();
}
// DEVICE UNDER TEST FUNCTIONS
function blessDeviceUnderTest() {
// Run any tests you require for you DUT
local testSuccess = myTestFunction();
// Attempt to bless this device, and send the result to the Factory Test Results Webhook
server.bless(testSuccess, function(blessSuccess) {
// Send identifying info and result to the test results webhook
agent.send("testresult", { "device_id": hardware.getdeviceid(),
"mac": imp.getmacaddress(),
"success": blessSuccess });
// Clear the WiFi credentials and reboot if blessing completes successfully
if (blessSuccess) {
imp.clearconfiguration();
imp.onidle(function() {
// Deep sleep for five seconds; triggers Squirrel reload
server.sleepfor(5);
});
}
});
}
// YOUR TEST CODE
function myTestFunction() {
// Run your tests here
// Returning false will prevent blessing and pass a "FAIL" to the test results webhook
return true;
}
// RUNTIME
// Use the Factory Tools library's isFactoryImp() call asynchronously in order to be sure
// the impFactory is online and has received device status data from the server
FactoryTools.isFactoryImp(function(isImpFactory) {
if (isImpFactory) {
// Device is an impFactory unit
// Register handler for receiving WiFi settings from agent
agent.on("fixture.set.wifi.credentials", function(data) {
// Record the WiFi settings we'll transmit to the DUT
ssid = data.ssid;
pwd = data.pwd;
// Now have the WiFi credentials, trigger the fixture flow
configureFactoryFixture();
}.bindenv(this));
// Request WiFi credentials from agent
agent.send("fixture.get.wifi.credentials", true);
} else {
// The next call need not be asynchronous since we can be sure
// at this point (we're executing in a callback) that the status
// data has now been received by the device (whatever it is)
if (FactoryTools.isDeviceUnderTest()) {
// Device is a production unit - test it
blessDeviceUnderTest();
} else {
server.log("This firmware is not running in the factory environment");
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment