Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active March 25, 2019 15:01
Show Gist options
  • Save ElectricImpSampleCode/c4bc593e3c7e71d9e0d98b902c681b8d to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/c4bc593e3c7e71d9e0d98b902c681b8d to your computer and use it in GitHub Desktop.
Factory Activation Example Code
// ------------------------------------------------------------------------------
// File: factory.activation.agent.nut
// Version: 1.0.4
//
// Copyright 2018 Electric Imp
//
// SPDX-License-Identifier: MIT
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------
// ***************************************
// ***** SETUP *****
// ***************************************
// IMPORTS
// Load the Factory Tools Library, which simplifies detecting Fixtures and DUTs
#require "FactoryTools.class.nut:2.1.0"
// CONSTANTS
// Replace the following string with your BlinkUp API key (provided to Electric Imp customers only)
const API_KEY = "YOUR_BLINKUP_API_KEY";
const API_URL = "https://api.electricimp.com/v1/setup_tokens";
// GLOBALS
local timeout = 0;
// Replace 'null' in the following line with a hard-coded plan ID
// if you are using a single plan ID (NOTE this is not recommended)
local planID = null;
// ***************************************
// ***** FACTORY FIXTURE AGENT CODE ****
// ***************************************
// Define the function that polls the impCloud for a claimed enrollment token
function poll(token, cb = null) {
// Create the request to send to the server
local path = API_URL + "/" + token;
local headers = { "Authorization": "Basic " + http.base64encode(API_KEY + ":") };
local result = http.get(path, headers).sendsync();
if (result.statuscode == 200) {
// Got a positive response from the server - try to decode the returned JSON
try {
local data = http.jsondecode(result.body);
if ("created_at" in data && data.created_at.len() > 0) {
// The key 'created_at' with a non-empty string indicates the token has
// been claimed, so return the data from the server via the callback
if (cb != null) cb(data);
return;
} else {
// Token not (yet) claimed
if (timeout > 0) {
timeout--;
} else {
if (cb != null) cb({"err": "Token claim timeout"});
return;
}
}
} catch (err) {
// Ignore JSON decode error - not much we can do here except poll again
}
}
// Poll again in two seconds’ time
imp.wakeup(2.0, function() {
poll(token, cb);
}.bindenv(this));
}
// This is a FACTORY FIXTURE AGENT routine to receive the token data sent by the
// DUT’s agent and to poll the server for the claimed token
http.onrequest(function(request, response) {
// Attempt to decode the JSON sent by the DUT's agent
try {
local data = http.jsondecode(request.body);
if ("id" in data) {
// Start polling for the claimed token
timeout = 30;
poll(data.id, function(pollData) {
if ("err" in pollData) {
server.error("Polling error: " + pollData.err);
} else {
// Make use of the data received from the poll
useData(pollData.agent_url, pollData.impee_id);
}
}.bindenv(this));
response.send(200, "OK");
} else {
response.send(400, "Bad data - no enrollment token included");
}
} catch (err) {
// Log the error and inform the DUT agent
server.error(err);
response.send(500, err);
}
});
function useData(agentURL, deviceID) {
// This function is a placeholder for the customers own. Here we simply
// log the DUT’s ID and agent URL, but real-world code might, for example,
// send this data to a label printer or laser etching station
server.log("Production Device device ID: " + deviceID);
server.log("Production Device agent URL: " + agentURL);
}
// ***************************************
// ***** DUT AGENT FUNCTIONS *****
// ***************************************
// Set up a receiver for "get.token" messages from the DUT - this is the DUT
// requesting an enrollment token from its own agent, not from the fixture
device.on("dut.get.token", function(dummy) {
// Set up an async request to the Electric Imp token server
local url = API_URL;
local headers = { "Content-Type": "application/json",
"Authorization": "Basic " + http.base64encode(API_KEY + ":") };
// If you are re-using a plan ID, it will be picked up and used here
local body = planID != null ? { "plan_id": planID } : {};
// Make the request for the token
local tokenRequest = http.post(url, headers, body);
// Issue the request and handle the response. The device can do nothing until
// it receives the token, so we can handle this request asynchronously
tokenRequest.sendasync(function(result) {
if (result.statuscode == 201) {
// Got a new token - indicated by the '201'
try {
// Convert the incoming data to a table
local data = http.jsondecode(result.body);
// It the token ('id') is present, send to the DUT
if ("id" in data) {
device.send("dut.set.token", data);
// Send the data to the fixture's agent (it will do the polling)
notifyFixture(data);
}
} catch (err) {
// JSON decode error - report it
server.error(err);
}
} else {
// We didn't get a token so report the status code
// NOTE Real-world code would inform the device or to make
// another attempt to retrieve a token
server.error("Code: " + result.statuscode);
}
});
});
function notifyFixture(data) {
// Send the token to the FIXTURE AGENT (not the DUT AGENT) so it can
// begin polling the server to see when the token is claimed
local url = imp.configparams.factory_fixture_url;
http.post(url, {}, http.jsonencode(data)).sendasync(function(response) {
if (response.statuscode != 200) {
// There was an issue - resend the data
// Real-world code should act according to the response status code
notifyFixture(data);
}
}.bindenv(this));
}
// ------------------------------------------------------------------------------
// File: factory.activation.device.nut
// Version: 1.0.4
//
// Copyright 2018 Electric Imp
//
// SPDX-License-Identifier: MIT
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// ------------------------------------------------------------------------------
// ***************************************
// ***** SETUP *****
// ***************************************
// IMPORTS
// Load the Factory Tools Library, which simplifies detecting Fixtures and DUTs
#require "FactoryTools.class.nut:2.1.0"
// Load the Button library to provide debouncing on the footswitch (if installed) and green button
// NOTE This example assumes you are using the impFactory appliance as your fixture
#require "Button.class.nut:1.2.0"
// CONSTANTS
// Replace the following strings with your factory WiFi credentials
// NOTE You might alternatively get these via a request to your own server
const SSID = "YOUR_FACTORY_WIFI_SSID";
const PASSWORD = "YOUR_FACTORY_WIFI_PASSWORD";
// Set the target WiFi Region code (see 'getRegionCode()', below, for values)
const WIFI_REGION = "US";
// This sets how long to wait (seconds) after triggering BlinkUp before allowing another
const BLINKUP_TIME = 10;
// GLOBALS
// Flag used to prevent new BlinkUp triggers while BlinkUp is running
local sendingBlinkUp = false;
local blinkUpData = { "ssid": SSID,
"pwd" : PASSWORD,
"planid" : "",
"token": "" };
// ***************************************
// ***** FACTORY FIXTURE FUNCTIONS *****
// ***************************************
function blinkupTriggered() {
// Trigger only when BlinkUp is not already running
if (!sendingBlinkUp) {
sendingBlinkUp = true;
imp.wakeup(BLINKUP_TIME, function() {
sendingBlinkUp = false;
});
// Send factory BlinkUp
server.factoryblinkup(SSID, PASSWORD, blinkupPin, BLINKUP_FAST | BLINKUP_ACTIVEHIGH);
}
}
function configureFactoryFixture() {
// Assign pins
greenBtn <- Button(hardware.pinC, DIGITAL_IN, Button.NORMALLY_HIGH, null, blinkupTriggered.bindenv(this));
footSwitch <- Button(hardware.pinH, DIGITAL_IN, Button.NORMALLY_HIGH, null, blinkupTriggered.bindenv(this));
ledGreen <- hardware.pinE;
ledRed <- hardware.pinF;
blinkupPin <- hardware.pinM;
// Initialize front panel LEDs to Off
ledRed.configure(DIGITAL_OUT, 0);
ledGreen.configure(DIGITAL_OUT, 0);
}
// ***************************************
// ** DEVICE UNDER TEST (DUT) FUNCTIONS **
// ***************************************
function blessDeviceUnderTest() {
// Run any tests you require for you DUT
local testSuccess = myTestFunction();
if (testSuccess) {
// DUT passed tests, so define a response handler
// for when the agent returns the enrollment token
// Do this BEFORE calling agent.send() so that the DUT
// is ready as soon as the data comes back (important if
// the factory network is slow)
agent.on("dut.set.token", function(data) {
blinkUpData.token = data.id;
blinkUpData.planid = data.plan_id;
// Attempt to bless this device
server.bless(testSuccess, function(blessSuccess) {
// Set the WiFi credentials and reboot if blessing completes successfully
if (blessSuccess) {
imp.setenroltokens(blinkUpData.planid, blinkUpData.token);
imp.setwificonfiguration(blinkUpData.ssid, blinkUpData.pwd);
imp.setcountry(getRegionCode(WIFI_REGION));
imp.reset();
}
}.bindenv(this));
}.bindenv(this));
// The message response handler defined, we can now request a token
agent.send("dut.get.token", true);
}
}
function getRegionCode(location = "US") {
local type = imp.info().type;
local returnCode = 0;
// Region codes data organized by territory code
// Each region has two values: the first for imp001 through imp004m, the second for imp005
// NOTE select "OT" for all regions than those listed (US, Canada, Europe/UK, Japan)
local codes = { "US" : [0x00005355, 0x00115858],
"CA" : [0x00005355, 0x03B64143],
"EU" : [0x00004247, 0x037F3045],
"JA" : [0x00004247, 0x03B8504A],
"OT" : [0x00004247, 0x03D75658] }
if (location in codes) {
returnCode = codes[location];
returnCode = returnCode[(type == "imp005" ? 1 : 0)];
} else {
server.error("Invalid region ID supplied - choosing US");
returnCode = codes.US[(type == "imp005" ? 1 : 0)];
}
server.log(format("Region code chosen: 0x%08X", returnCode));
return returnCode;
}
// ***************************************
// ***** YOUR HARDWARE TEST CODE *****
// ***************************************
function myTestFunction() {
// Add your tests here
// NOTE Returning false will prevent blessing
return true;
}
// ***************************************
// ***** RUNTIME START *****
// ***************************************
// Use the Factory Tools library's 'isFactoryImp()' call asynchronously in order to be sure
// the fixture is online and has received device status data from the server
FactoryTools.isFactoryImp(function(isImpFactory) {
if (isImpFactory) {
// Device is a factory BlinkUp fixture, eg. impFactory
configureFactoryFixture();
} 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()) {
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