Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active April 22, 2022 10:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ElectricImpSampleCode/360de8bc774cb4a933a041086c108fb0 to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/360de8bc774cb4a933a041086c108fb0 to your computer and use it in GitHub Desktop.
impOS 42 UDP Local Networking Example
// ********** Imports **********
#require "Rocky.class.nut:2.0.2"
// ********** Web UI **********
const HTML_STRING = @"<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>UDP Demo</title>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css'>
<link href='https://fonts.googleapis.com/css?family=Rubik|Questrial' rel='stylesheet'>
<style>
.uicontent {border: 2px solid #33cc00;}
.container {padding: 20px;}
.center {margin-left: auto; margin-right: auto; margin-bottom: auto; margin-top: auto;}
.tabborder {width: 20%%;}
.tabcontent {width: 60%%;}
.btn-success {background-color: #33cc00;}
body {background-color: #333333;}
p {color: white; font-family: Questrial, sans-serif; font-size: 1em;}
h4 {color: white; font-family: Questrial, sans-serif;}
td {color: white; font-family: Questrial, sans-serif;}
hr {border-color: #33cc00;}
img {max-width: 100%%; height: auto;}
@media only screen and (max-width: 640px) {
.container {padding: 5px}
.uicontent {border: 0px}
.col-2 {max-width: 0%%; flex: 0 0 0%%}
.col-8 {max-width: 100%%; flex: 0 0 100%%}
}
</style>
</head>
<body>
<div class='container'>
<div class='uicontent'>
<div class='row'>
<div class='col-2'>&nbsp;</div>
<div class='col-8'>
<p>&nbsp;</p>
<h4 align='center'>UDP Demo<br />&nbsp;</h4>
<div>
<h4 align='center'>Nodes</h4>
<p class='node-list text-center'><span></span></p>
<div align='center'>
<button type='submit' class='btn btn-success' id='updatebutton' style='width:200px;font-family:Rubik;'>Update List</button>
</div>
</div>
<p>&nbsp;</p>
</div>
<div class='col-2'>&nbsp;</div>
</div>
</div>
</div>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<script>
var agenturl = '%s';
// Get initial readings
getState(updateReadout);
// Buttons
$('#updatebutton').click(updateState);
// Functions
function updateReadout(j) {
var d = JSON.parse(j);
showNodes(d);
}
function showNodes(nodes) {
if (nodes.length == 0) {
// No alarms so just show a simple message
$('.node-list span').text('No Nodes found');
} else {
// Build an HTML table to show the alarms
var h = '<table width=""100%%"" class=""table table-striped table-sm"">';
h = h + '<tr><th>Node</th><th>IP</th><th>Device ID</th><th>Reading</th><th>Timestamp</th></tr>';
for (var i = 0 ; i < nodes.length ; i++) {
let node = nodes[i];
if (node.value == -99.0) {
node.value = ' ';
} else {
node.value = node.value.toFixed(1).toString() + '&deg;C';
}
h = h + '<tr><td width=""10%%"" align=""center"">' + (i + 1).toString() + '</td><td width=""20%%"" align=""center"">' + node.address + '</td><td width=""20%%"" align=""center"">' + node.id + '</td><td width=""20%%"" align=""center"">' + node.value + '</td><td width=""30%%"" align=""center"">' + node.timestamp + '</td></tr>';
}
h = h + '</table>';
$('.node-list span').html(h);
}
}
function updateState() {
$('.node-list span').text('Updating...');
getState(updateReadout);
}
function getState(c) {
// Request the current data
$.ajax({
url: agenturl + '/nodes',
type: 'GET',
cache: false,
success: function(r) {
if (c) c(r);
},
error: function(xhr, sta, err) {
if (err) $('.clock-status span').text(err);
}
});
}
</script>
</body>
</html>";
// ********** Globals **********
local nodes;
local api;
local count = 0;
// ********** Function Definitions **********
function debugAPI(context, next) {
// Display a UI API activity report
server.log("API received a request at " + time() + ": " + context.req.method.toupper() + " @ " + context.req.path.tolower());
if (context.req.rawbody.len() > 0) server.log("Request body: " + context.req.rawbody.tolower());
// Invoke the next middleware
next();
}
function sortNodes(a, b) {
local p = split(a.address, ".");
local x = p[3].tointeger();
p = split(b.address, ".");
local y = p[3].tointeger();
if (x < y) return -1;
if (x > y) return 1;
return 0;
}
// ********** Device message handlers **********
device.on("gateway.routed.reading", function(data) {
count++;
server.log("Data received from node " + data.node.address + " at " + data.timestamp + " (reading " + count + ")");
server.log("Recorded temperature: " + format("%.2f", data.reading.tofloat()) + " Celsius");
});
device.on("gateway.routed.forecast", function(data) {
count++;
server.log("Data received from node " + data.node.address + " at " + data.timestamp + " (reading " + count + ")");
server.log("Forecast: " + data.forecast);
});
device.on("gateway.set.nodes", function(data) {
// Update the local nodes list
nodes = data;
});
// ********** Runtime Start **********
nodes = [];
api = Rocky();
api.use(debugAPI);
// Set up UI access security: HTTPS only
api.authorize(function(context) {
// Mandate HTTPS connections
if (context.getHeader("x-forwarded-proto") != "https") return false;
return true;
});
api.onUnauthorized(function(context) {
// Incorrect level of access security
context.send(401, "Insecure access forbidden");
});
// GET to root: just return the UI HTML
api.get("/", function(context) {
context.send(200, format(HTML_STRING, http.agenturl()));
});
// GET to /dimmer: return the dimmer status
api.get("/nodes", function(context) {
nodes.sort(sortNodes);
context.send(200, http.jsonencode(nodes));
});
// ********** Imports **********
#require "JSONEncoder.class.nut:2.0.0"
#require "JSONParser.class.nut:1.0.1"
// ********** Early setup code **********
// Keep the hub running even if the Internet connection is lost
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 10);
// ********** Constants **********
// Message source indicators
enum UDP_MESSAGE_SOURCE {
HUB = "IMPH",
NODE = "IMPN"
}
// Message type indicators
enum UDP_MESSAGE_TYPE {
RESET = "RT",
GENERAL = "GL"
}
// UDP constants
const UDP_LONGEST_IMPLEMENTED_MESSAGE_LENGTH = 141; // 10 bytes header + 99 bytes data + 32 bytes footer
const UDP_LOW_MEMORY_THRESHOLD = 20000;
const UDP_PORT = 20001;
// Node state constants
enum NODE_STATE {
NO_DATA_RECEIVED = 1,
DATA_RECEIVED = 2
}
// ********** Globals **********
local interface;
local udpsocket;
local nodes;
local lastKey;
local signingKey;
local keys;
local keyIndex;
local broadcastAddress;
local hubAddress;
local readingTimer;
local ping = true;
// ********** Function Definitions **********
function wifiRXCallback(fromAddress, fromPort, toAddress, toPort, data) {
// This is the data-received callback for all incoming UDP datagrams
// Log the incomoimg receipt and then decode the datagram
if (toAddress != hubAddress && toAddress != broadcastAddress) {
server.log("RX from: " + formatAddress(fromAddress) + ":" + fromPort + " (Unicast to other Node)");
return;
} else {
server.log("RX from: " + formatAddress(fromAddress) + ":" + fromPort + " " + (toAddress == hubAddress ? "(Unicast to this Hub)" : "(Broadcast packet)"));
}
local response = decodeMessage(data, UDP_MESSAGE_SOURCE.NODE);
if ("command" in response) {
server.log(JSONEncoder.encode(response));
if (response.command == "imp.identify") {
// The hub has received a identification message from a node
local node;
if (nodes == null) nodes = [];
if (nodes.len() > 0) {
// See if we have the node recorded already
local got = false;
foreach (node in nodes) {
if (node.id == response.id) {
got = true;
break;
}
}
if (!got) {
// If the node is new, record its address, etc.
node = makeNewNode(fromAddress, response.seqnum, response.id);
nodes.append(node);
} else {
// The node is not new, so just update its details
node = getNodeFromID(response.id);
if (node.address != fromAddress) node.address = fromAddress;
node.seqnum = response.seqnum;
node.enrolled = false;
}
} else {
// The node is new, so record its address, etc.
node = makeNewNode(fromAddress, response.seqnum, response.id);
nodes.append(node);
}
// Finally, respond to the identification message with an ACK
local payload = JSONEncoder.encode({"command":"imp.identify.ack"});
if (node != null) send(node, payload);
} else if (response.command == "imp.request.enroll") {
// The hub has received an enroll request from a node
local node = getNodeFromID(response.id);
if (node != null) {
// Node has responded with a new sequence number, so apply it
server.log("New NODE SN: " + response.seqnum);
node.seqnum = response.seqnum;
node.enrolled = true;
}
// Update the agent's list of current nodes
agent.send("gateway.set.nodes", nodes);
} else if (response.command == "imp.reading.ack") {
// The hub has received a data-bearing message from a node
local node = getNodeFromID(response.id);
if (node != null) {
// First check the sequencing
local diff = response.seqnum - node.seqnum;
if (diff > 0 && diff < 101) {
node.seqnum = response.seqnum;
node.state = NODE_STATE.DATA_RECEIVED;
node.value = response.data.tofloat();
node.timestamp = time();
server.log("Node SN: " + node.seqnum);
// Send the reading to the agent
local data = { "node" : node,
"reading": response.data,
"timestamp": time() };
agent.send("gateway.routed.reading", data);
agent.send("gateway.set.nodes", nodes);
} else {
server.error("HUB Bad sequence number - delta: " + diff);
}
// Just check IP address hasn't changed (and update if it has)
if (node.address != fromAddress) node.address = fromAddress;
}
} else if (response.command == "imp.forecast.ack") {
// The hub has received a forecast-bearing message from a node
local node = getNodeFromID(response.id);
if (node != null) {
// First check the sequencing
local diff = response.seqnum - node.seqnum;
if (diff > 0 && diff < 101) {
node.seqnum = response.seqnum;
node.timestamp = time();
node.state = NODE_STATE.DATA_RECEIVED;
// Send the reading to the agent
local data = { "node" : node,
"forecast": response.forecast,
"timestamp": time() };
agent.send("gateway.routed.forecast", data);
agent.send("gateway.set.nodes", nodes);
} else {
server.error("HUB Bad sequence number - delta: " + diff);
}
// Just check IP address hasn't changed (and update if it has)
if (node.address != fromAddress) node.address = fromAddress;
}
}
} else {
// There was an error decoding the message
if ("err" in response) server.error(response.err);
}
}
function makeNewNode(fromAddress, seqNumber, msgID) {
// Common code for node creation
local newNode = {"address":fromAddress,
"seqnum":seqNumber,
"id":msgID,
"enrolled":false,
"state":NODE_STATE.NO_DATA_RECEIVED,
"value":-99.0,
"timestamp":time()};
return newNode;
}
function getNodeFromAddress(addr) {
// Return the node with the specified IP address, or null
foreach (node in nodes) {
if (node.address == addr) return node;
}
return null;
}
function getNodeFromID(id) {
// Return the node with the specified device ID, or null
foreach (node in nodes) {
if (node.id == id) return node;
}
return null;
}
function decodeMessage(data, expectedMessageSource) {
// Determine whether the message is genuine or not. if it is not, just fail
// This is legitimate with UDP comms, which has no ACKS to grind...
// Is the message well sized, and have we space for it?
if (data.len() > UDP_LONGEST_IMPLEMENTED_MESSAGE_LENGTH || imp.getmemoryfree() < UDP_LOW_MEMORY_THRESHOLD) return {"err":"Bad message length"};
// Check the header start marker is from the hub
data.seek(0);
local messageSource = data.readstring(4);
if (messageSource != expectedMessageSource) return {"err":"Bad message source: " + messageSource};
// Get the sequence number
local messageSeqNumber = swap4(data.readn('i'));
// Check the code
local messageType = data.readstring(2);
if (messageType != UDP_MESSAGE_TYPE.GENERAL && packetType != UDP_MESSAGE_TYPE.RESET) return {"err":"Bad message type: " + messageType};
// Check the crytographic signature (final 32 bytes of data)
data.seek(-32, 'e');
local messageSignature = data.readblob(32);
data.seek(0);
local signedMessage = data.readblob(data.len() - 32);
local count = 0;
local done = false;
// Iterate through the available keys to see if the message
// was signed by one of them
do {
lastKey = signingKey;
signingKey = keys[keyIndex];
local expectedSignature = crypto.hmacsha256(signedMessage, signingKey);
if (expectedSignature != null && crypto.equals(expectedSignature, messageSignature)) {
// Key worked
done = true;
} else {
// Key failed; try the next one
keyIndex++;
if (keyIndex > 3) keyIndex = 0;
count++;
// Tried all the available keys? Bail
if (count > keys.len() - 1) return {"err":"Bad key"};
}
} while (!done);
// Extract and return the message's payload
data.seek(10);
local messageData = data.readblob(data.len() - data.tell() - 32);
local resp = JSONParser.parse(messageData.tostring());
resp.seqnum <- messageSeqNumber;
resp.type <- messageType;
return resp;
}
function send(node, payload, messageType = UDP_MESSAGE_TYPE.GENERAL) {
// Send the string payload to the specified address via UDP - after signing the data
// Increase the sequence number
node.seqnum++;
node.state == NODE_STATE.NO_DATA_RECEIVED
local message = encodeMessage(payload, lastKey, UDP_MESSAGE_SOURCE.HUB, messageType, node.seqnum);
udpsocket.send(node.address, UDP_PORT, message);
}
function encodeMessage(data, key, messageSource, messageType, messageSeqNumber) {
// Construct a signed, suitable coded message for sending
local message = blob(42 + data.len()) // 10 bytes header + body length + 32 bytes footer
// Write the message header:
// Bytes 1-4: Four-character message type indicator
// 5-8: 32-bit message sequence number
// 9-10: Two-character packet type indicator
message.seek(0);
message.writestring(messageSource);
message.writen(swap4(messageSeqNumber), 'i');
message.writestring(messageType);
// Write the message body (assumes an input string)
if (data.len() > 0) message.writestring(data);
// Write the message footer:
// 32-byte 256-bit HMACSHA as message signature
message.seek(0);
local signature = crypto.hmacsha256(message.readblob(message.len() - 32), key);
message.seek(message.len() - 32);
message.writeblob(signature);
return message;
}
function interfaceHandler(state) {
// Called whenever the local networking interface state changes
server.log("HUB State: " + state);
if (state == imp.net.CONNECTED && udpsocket == null) {
// We're connected, so initiate UDP
server.log("Setting up UDP");
udpsocket = interface.openudp(wifiRXCallback, UDP_PORT);
// The hub will now wait for 'identify' packets from nearby nodes
} else if ((imp.net.WIFI_STOPPED_UNHAPPY || imp.net.WIFI_STOPPED) && udpsocket != null) {
// The WiFi network has gone, null 'udpsocket' so we know it's ready for re-use
server.log("Closing UDP");
udpsocket = null;
}
}
function requestReadings() {
// Ask for data from all known nodes - recorded in the 'nodes' array
if (nodes != null && nodes.len() > 0) {
foreach (node in nodes) {
if (node.enrolled) {
// Send a data request to every known device that is enrolled
server.log("Asking NODE @ " + formatAddress(node.address) + " for a reading");
local payload = JSONEncoder.encode({"command":"imp.request.reading"});
// If we didn't receive a response last time, bump the sequence number
if (node.state == NODE_STATE.NO_DATA_RECEIVED) node.seqnum += 1;
// Transmit the request to the node
send(node, payload);
}
}
}
// Set a timer to get another set of readings in 60s' time
imp.wakeup(60, requestReadings);
}
function formatAddress(ip) {
// Reformat an IP address for logging
local ps = split(ip, ".");
local op = ""
foreach (oct in ps) {
if (oct.len() == 2) oct = "0" + oct;
if (oct.len() == 1) oct = "00" + oct;
op += (oct + ".");
}
return op.slice(0, op.len() - 1);
}
// ********** RUNTIME START **********
// Set up the keys we will be using for the demo
// A real-world application would source these elsewhere, eg. on-board flash or via the agent
keys = [];
keys.append("\x58\xED\x26\xD3\x90\x08\x4B\x4E\xA6\x92\x2E\x8A\x7E\x0A\x65\xBE\x19\x72\xB8\x1F\x48\x10\x4A\x1A\x89\xA1\x61\x2B\x7B\x57\xEB\x01");
keys.append("\x7C\xD7\x7C\x25\x7A\x7A\x40\xE5\xB7\x97\x79\x80\x85\x77\x82\x46\xBB\xD8\x20\xE4\xBF\xFD\x40\xCA\x9C\x9A\xCB\x8D\x2A\x58\x01\xAB");
keys.append("\xB6\x57\x93\xAE\x2A\xF3\x46\x9C\x84\x04\xD2\xC9\xE1\xD4\x8C\x08\xDC\xDC\x98\x55\xDD\x52\x4E\xAE\x9D\x56\x0D\xFA\x80\x8E\x01\x7D");
keys.append("\xB3\xED\x7A\x18\xB8\x5B\x40\xE5\xB6\x95\x50\x9A\x3B\x8B\x3B\x1A\xB7\x1C\x38\xEC\xEE\xA0\x45\x70\x8D\x55\xDA\x70\x13\x9A\xE2\xDD");
// Pick a random key to start with
keyIndex = (4.0 * math.rand() / RAND_MAX).tointeger();
signingKey = keys[keyIndex];
lastKey = signingKey;
// Log the hub's IP address
local i = imp.net.info();
hubAddress = i.ipv4.address;
broadcastAddress = i.ipv4.broadcast;
server.log("HUB IP: " + hubAddress);
server.log("Broadcast IP: " + broadcastAddress);
// Open the WiFi connection for local use
server.log("Opening WiFi for local networking use");
interface = imp.net.open({"interface": "wl0"}, interfaceHandler);
// Start asking nodes, if any, for readings
requestReadings();
// ********** Imports **********
#require "JSONEncoder.class.nut:2.0.0"
#require "JSONParser.class.nut:1.0.1"
#require "HTS221.device.lib.nut:2.0.2"
#require "WS2812.class.nut:3.0.0"
// ********** Early setup code **********
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 10);
// ********** Constants **********
// Message source indicators
enum UDP_MESSAGE_SOURCE {
HUB = "IMPH",
NODE = "IMPN"
}
// Message type indicators
enum UDP_MESSAGE_TYPE {
RESET = "RT",
GENERAL = "GL"
}
// UDP constants
const UDP_LONGEST_IMPLEMENTED_MESSAGE_LENGTH = 141; // 10 bytes header + 99 bytes data + 32 bytes footer
const UDP_LOW_MEMORY_THRESHOLD = 20000;
const UDP_PORT = 20001;
// ********** Globals **********
local sensor;
local interface;
local udpsocket;
local lastKey;
local signingKey;
local keys;
local keyIndex;
local currentSeqNumber;
local broadcastAddress;
local nodeAddress;
local enrollTimer;
local lastMessageTimestamp;
local isEnrolled = false;
local led;
local spi;
// ********** Function Definitions **********
function attemptEnroll() {
if (!isEnrolled) {
// Node not enrolled yet, so try ask again in 30s
enrollTimer = imp.wakeup(30, attemptEnroll);
// Broadcast an 'identify' packet - only a hub will respond
local payload = JSONEncoder.encode({"command":"imp.identify","id":hardware.getdeviceid()});
sendMessage(broadcastAddress, payload);
}
}
function hubWatchdog() {
// Simple watchdog to check for an absent hub
local diff = time() - lastMessageTimestamp;
if (diff > 120) {
// No hub messages seen for 120s (2m) - has the hub rebooted?
// It might have, so re-enroll
isEnrolled = false;
attemptEnroll();
server.log("NODE says HUB down for 2+ minutes - re-enrolling");
} else {
// All good - check again in 10s
imp.wakeup(10, hubWatchdog);
}
}
function wifiRXCallback(fromAddress, fromPort, toAddress, toPort, data) {
// Node has eceived a message - log then decode it
if (toAddress != nodeAddress && toAddress != broadcastAddress) {
server.log("RX from: " + formatAddress(fromAddress) + ":" + fromPort + " (Unicast to other Node)");
return;
} else {
server.log("RX from: " + formatAddress(fromAddress) + ":" + fromPort + " " + (toAddress == nodeAddress ? "(Unicast to this Node)" : "(Broadcast packet)"));
}
local response = decodeMessage(data, UDP_MESSAGE_SOURCE.HUB);
if ("command" in response) {
server.log("Payload: " + JSONEncoder.encode(response));
if (response.command == "imp.identify.ack") {
// Received an enrollment confirmation from the hub, so update the node's state
lastMessageTimestamp = time();
currentSeqNumber = math.pow(-1, math.rand() % 2).tointeger() * math.rand();
server.log("NODE SN: " + currentSeqNumber);
isEnrolled = true;
// Send the new sequence number to the hub
local payload = JSONEncoder.encode({"command":"imp.request.enroll","id":hardware.getdeviceid()});
sendMessage(fromAddress, payload);
// Start the 'hub missing' watchdog
hubWatchdog();
} else if (response.command == "imp.request.reading") {
// The hub has requested a 16-bit sensor reading, so take one and send it back
// Only continue if the sequence number is valid
lastMessageTimestamp = time();
server.log("--> SN: " + response.seqnum);
if (checkSequenceNumber(response.seqnum)) {
led.set(0, [255,0,0]).draw();
sensor.read(function(result) {
if ("err" in result) {
// Whoops! Report the error
server.error("A sensor error occurred: " + result.err);
} else {
// Got a reading, so get the temperature and add it into
// the message in the form the hub expects
local temp = format("%.4f", result.temperature);
local payload = JSONEncoder.encode({"command":"imp.reading.ack","data":temp,"id":hardware.getdeviceid()});
sendMessage(fromAddress, payload);
led.set(0, [0,0,0]).draw();
}
}.bindenv(this));
}
server.log("NEW SN: " + currentSeqNumber);
}
} else {
// There was an error decoding the message
if ("err" in response) server.error(response.err);
}
}
function checkSequenceNumber(messageSeqNumber) {
// Check that the incoming sequence number is valid
local diff = messageSeqNumber - currentSeqNumber;
if (diff <= 0 || diff > 100) {
server.log("Bad sequence number - delta: " + diff);
return false;
}
// Update the current sequence number for the next send
currentSeqNumber = messageSeqNumber + 1;
return true;
}
function decodeMessage(data, expectedMessageSource) {
// Determine whether the message is genuine or not. if it is not, just fail
// This is legitimate with UDP comms, which has no ACKS to grind...
// Is the message well sized, and have we space for it?
if (data.len() > UDP_LONGEST_IMPLEMENTED_MESSAGE_LENGTH || imp.getmemoryfree() < UDP_LOW_MEMORY_THRESHOLD) return {"err":"Bad message length"};
// Check the header start marker is from the hub
data.seek(0);
local messageSource = data.readstring(4);
if (messageSource != expectedMessageSource) {
// Bad message type, but ignore message from nodes
if (messageSource == UDP_MESSAGE_SOURCE.NODE) return {};
return {"err":"Bad message source: " + messageSource};
}
// Get the sequence number
local messageSeqNumber = swap4(data.readn('i'));
// Check the code
local messageType = data.readstring(2);
if (messageType != UDP_MESSAGE_TYPE.GENERAL && packetType != UDP_MESSAGE_TYPE.RESET) return {"err":"Bad message type: " + messageType};
// Check the crytographic signature (final 32 bytes of data)
data.seek(-32, 'e');
local messageSignature = data.readblob(32);
data.seek(0);
local signedMessage = data.readblob(data.len() - 32);
local count = 0;
local done = false;
// Iterate through the available keys to see if the message
// was signed by one of them
do {
lastKey = signingKey;
signingKey = keys[keyIndex];
local expectedSignature = crypto.hmacsha256(signedMessage, signingKey);
if (expectedSignature != null && crypto.equals(expectedSignature, messageSignature)) {
// Key worked
done = true;
} else {
// Key failed; try the next one
keyIndex++;
if (keyIndex > 3) keyIndex = 0;
count++;
// Tried all the available keys? Bail
if (count > keys.len() - 1) return {"err":"Bad key"};
}
} while (!done);
// Extract and return the message's payload and other data
data.seek(10);
local messageData = data.readblob(data.len() - data.tell() - 32);
local resp = JSONParser.parse(messageData.tostring());
resp.seqnum <- messageSeqNumber;
resp.type <- messageType;
return resp;
}
function sendMessage(fromAddress, payload, messageType = UDP_MESSAGE_TYPE.GENERAL) {
// Send the string payload to the specified address via UDP - after signing the data
local message = encodeMessage(payload, lastKey, UDP_MESSAGE_SOURCE.NODE);
udpsocket.send(fromAddress, UDP_PORT, message);
}
function encodeMessage(data, key, messageSource, messageType = UDP_MESSAGE_TYPE.GENERAL) {
// Construct a signed, suitable coded message for sending
local message = blob(42 + data.len()) // 10 bytes header + body length + 32 bytes footer
// Write header
message.seek(0);
message.writestring(messageSource);
message.writen(swap4(currentSeqNumber), 'i'); // Obfuscate the sequence number
message.writestring(messageType);
// Write body (assumes an input string)
if (data.len() > 0) message.writestring(data);
// Write footer
message.seek(0);
local signature = crypto.hmacsha256(message.readblob(message.len() - 32), key);
message.seek(message.len() - 32);
message.writeblob(signature);
return message;
}
function interfaceHandler(state) {
// Called when the local networking interface state changes
server.log("NODE State: " + state);
if (state == imp.net.CONNECTED && udpsocket == null) {
// We're connected, so initiate UDP
server.log("Setting up UDP");
udpsocket = interface.openudp(wifiRXCallback, UDP_PORT);
// Now attempt to enroll
attemptEnroll();
} else if ((imp.net.WIFI_STOPPED_UNHAPPY || imp.net.WIFI_STOPPED) && udpsocket != null) {
// The WiFi network has gone, null 'udpsocket' so we know it's ready for re-use
server.log("Closing UDP");
udpsocket = null;
isEnrolled = false;
}
}
function formatAddress(ip) {
// Reformat an IP address for logging
local ps = split(ip, ".");
local op = ""
foreach (oct in ps) {
if (oct.len() == 2) oct = "0" + oct;
if (oct.len() == 1) oct = "00" + oct;
op += (oct + ".");
}
return op.slice(0, op.len() - 1);
}
// ********** RUNTIME START **********
// Write the keys we will be using.
// A real-world application would source these elsewhere, eg. on-board flash or via the agent
keys = [];
keys.append("\x58\xED\x26\xD3\x90\x08\x4B\x4E\xA6\x92\x2E\x8A\x7E\x0A\x65\xBE\x19\x72\xB8\x1F\x48\x10\x4A\x1A\x89\xA1\x61\x2B\x7B\x57\xEB\x01");
keys.append("\x7C\xD7\x7C\x25\x7A\x7A\x40\xE5\xB7\x97\x79\x80\x85\x77\x82\x46\xBB\xD8\x20\xE4\xBF\xFD\x40\xCA\x9C\x9A\xCB\x8D\x2A\x58\x01\xAB");
keys.append("\xB6\x57\x93\xAE\x2A\xF3\x46\x9C\x84\x04\xD2\xC9\xE1\xD4\x8C\x08\xDC\xDC\x98\x55\xDD\x52\x4E\xAE\x9D\x56\x0D\xFA\x80\x8E\x01\x7D");
keys.append("\xB3\xED\x7A\x18\xB8\x5B\x40\xE5\xB6\x95\x50\x9A\x3B\x8B\x3B\x1A\xB7\x1C\x38\xEC\xEE\xA0\x45\x70\x8D\x55\xDA\x70\x13\x9A\xE2\xDD");
// Pick a random key to start with
keyIndex = (4.0 * math.rand() / RAND_MAX).tointeger();
signingKey = keys[keyIndex];
lastKey = signingKey;
currentSeqNumber = math.pow(-1, math.rand() % 2).tointeger() * math.rand();
// Set up the sensor
hardware.i2c89.configure(CLOCK_SPEED_400_KHZ);
sensor = HTS221(hardware.i2c89);
sensor.setResolution(16, 8);
sensor.setMode(HTS221_MODE.CONTINUOUS, 7);
// Set up the LED
spi = hardware.spi257;
spi.configure(MSB_FIRST, 7500);
hardware.pin1.configure(DIGITAL_OUT, 1);
led = WS2812(spi, 1);
// Get the broadcast address
local i = imp.net.info();
nodeAddress = i.ipv4.address;
broadcastAddress = i.ipv4.broadcast;
server.log("Node IP: " + nodeAddress);
server.log("Broacast IP: " + broadcastAddress);
// Open the WiFi connection for local use
server.log("Opening WiFi for local networking");
interface = imp.net.open({"interface":"wl0"}, interfaceHandler);
lastMessageTimestamp = time();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment