Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active October 17, 2018 15:23
Show Gist options
  • Save ElectricImpSampleCode/ead5412517ebc16fd235a30ed99e4176 to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/ead5412517ebc16fd235a30ed99e4176 to your computer and use it in GitHub Desktop.
Zigbee Example
// Zigbee Lamp Simulator - Gateway Agent Code
// Copyright Electric Imp, Inc. 2018
// IMPORTS
#require "Rocky.class.nut:2.0.1"
// CONSTANTS
const HTML_STRING = @"
<!DOCTYPE html>
<html lang='en-US'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<link rel='stylesheet' href='https://netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css'>
<link href='https://fonts.googleapis.com/css?family=Abel|Oswald' rel='stylesheet'>
<link rel='shortcut icon' href='https://smittytone.github.io/images/ico-imp.ico'>
<title>Zigbee Lamp Demo</title>
<style>
.center {margin-left: auto; margin-right: auto; margin-bottom: auto; margin-top: auto;}
body {background-color: #07a9a9;}
p {color: white; font-family: Abel, sans-serif;}
p.colophon {font-family: Oswald, sans-serif;}
p.header {font-size: 22px;color: #54585A; font-weight:bold;}
p.controls {font-size: 18px;}
h2 {color: #FFCD00; font-family: Abel, sans-serif; font-weight:bold;}
h4 {color: white; font-family: Abel, sans-serif;}
td {color: white; font-family: Abel, sans-serif;}
hr {border-color: white;}
.container {padding: 20px;}
.uicontent {border: 2px solid white;}
.btn-dark {width: 200px;}
@media only screen and (max-width: 768px) {
.container {padding: 0px;}
.uicontent {border: 0px;}
.btn-dark {width: 160px;}
.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='row uicontent' align='center'>
<div class='col'>
<!-- Title and Data Readout Row -->
<div class='row' align='center'>
<div class='col'>
<h2 class='text-center'>&nbsp;<br />Zigbee Lamp Demo<br />&nbsp;</h2>
<h4 class='text-center lamp-status'><span>The Lamp is On</span></h4>
<h4 class='text-center connect-status'><span>The Lamp is Online</span></h4>
<p class='text-center error-message'><i><span></span></i></p>
</div>
</div>
<!-- Controls and Settings -->
<div class='row'>
<div class='col-3'>&nbsp;</div>
<div class='col-6' style='border: 1px solid white;''>
<p class='header' align='center'>Lamp Settings</p>
<div class='row'>
<div class='col-6 power-button' align='center' style='font-family:Abel, sans-serif'>
<button class='btn btn-dark' type='submit' id='poweron'>Turn Lamp On</button>
</div>
<div class='col-6 power-button' align='center' style='font-family:Abel, sans-serif'>
<button class='btn btn-dark' type='submit' id='poweroff'>Turn Lamp Off</button>
</div>
</div>
<p style='font-size:8px;'>&nbsp;</p>
<div class='row'>
<div class='reboot-button col-12' align='center' style='font-family:Abel, sans-serif'>
<button class='btn btn-dark' type='submit' id='toggler'>Toggle Lamp</button>
</div>
</div>
&nbsp;
</div>
<div class='col-3'>&nbsp;</div>
</div>
<p style='font-size:14px;'>&nbsp;</p>
<div class='row'>
<div class='col-3'>&nbsp;</div>
<div class='col-6' style='border: 1px solid white;''>
<p class='header' align='center'>Advanced Settings</p>
<p style='font-size:8px;'>&nbsp;</p>
<div class='identify-button' align='center' style='font-family:Abel, sans-serif'>
<button class='btn btn-dark' type='submit' id='identifier'>Identify Lamp</button>
</div>
<hr>
<div class='debug-checkbox' style='color:white;font-family:Abel, sans-serif' align='center'>
<input type='checkbox' name='debug' id='debug' value='debug'> Debug Mode<br />&nbsp;
</div>
</div>
<div class='col-3'>&nbsp;</div>
</div>
<!-- Colophon -->
<p class='colophon'><small>Zigbee Lamp Demo &copy; Electric Imp, 2018</small></p>
</div>
</div>
</div>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<script>
var agenturl = '%s';
var power = true;
// Get initial readings
getState(updateReadout);
// Set UI click actions
$('#toggler').click(doToggle);
$('#poweron').click(setPowerOn);
$('#poweroff').click(setPowerOff);
$('#debug').click(setDebug);
$('#identifier').click(doIdentify);
function updateReadout(data) {
if (data.error) {
$('.error-message span').text(data.error);
} else {
power = data.on;
$('.lamp-status span').text(power ? 'The Lamp is On' : 'The Lamp is Off');
$('.connect-status span').text(data.connected ? 'The Lamp is Online' : 'The Lamp is Offline');
document.getElementById('debug').checked = data.debug;
}
setTimeout(function() {
getState(updateReadout);
}, 30000);
}
function getState(callback) {
// Request the current data
$.ajax({
url : agenturl + '/state',
type: 'GET',
success : function(response) {
response = JSON.parse(response);
if (callback) {
callback(response);
}
},
cache: false
});
}
function doToggle() {
// Trigger a device restart
$.ajax({
url : agenturl + '/light',
type: 'POST',
data: JSON.stringify({ 'light' : 'toggle' }),
success : function(response) {
power = !power
$('.lamp-status span').text('The Lamp is ' + (power ? 'On' : 'Off'));
},
cache: false
});
}
function doIdentify() {
// Trigger a lamp identify
$.ajax({
url : agenturl + '/actions',
type: 'POST',
data: JSON.stringify({ 'action' : 'identify' }),
cache: false
});
}
function setPowerOn() {
// Tell the device to light up
$.ajax({
url : agenturl + '/light',
type: 'POST',
data: JSON.stringify({ 'light' : 'on' }),
success : function(response) {
power = true;
$('.lamp-status span').text('The Lamp is On');
},
cache: false
});
}
function setPowerOff() {
// Tell the device to light up
$.ajax({
url : agenturl + '/light',
type: 'POST',
data: JSON.stringify({ 'light' : 'off' }),
success : function(response) {
power = false;
$('.lamp-status span').text('The Lamp is Off');
},
cache: false
});
}
function setDebug() {
// Tell the device to enter or leave debug mode
$.ajax({
url : agenturl + '/actions',
type: 'POST',
data: JSON.stringify({ 'action' : 'debug', 'debug' : document.getElementById('debug').checked }),
cache: false
});
}
</script>
</body>
</html>
";
// GLOBALS
local lampState = null;
local lampAPI = null;
// FUNCTIONS
function setDefaultState() {
// Set the lamp's initial state - this will be updated shortly
lampState = {};
lampState.debug <- true;
lampState.on <- true;
}
// RUNTIME START
// Initalise the state of the lamp
setDefaultState();
// Set up device message handlers
device.on("send.state", function(devState) {
lampState = devState;
server.log("Lamp state: " + (lampState.on ? "on" : "off"));
});
// Set up the lamp's Internet API
lampAPI = Rocky();
lampAPI.get("/", function(context) {
// Send the UI, eg. to a web browser
context.send(200, format(HTML_STRING, http.agenturl()));
});
lampAPI.get("/state", function(context) {
// Handle a GET request made to /state: return a state structure
local data = {};
data.on <- lampState.on;
data.debug <- lampState.debug;
local isOnline = device.isconnected();
data.connected <- isOnline ? "1" : "0";
context.send(200, http.jsonencode(data, {"compact":true}));
});
lampAPI.post("/light", function(context) {
// Handle a POST request made to /light - light control
local data;
try {
// Attempt to decode the received data as JSON
data = http.jsondecode(context.req.rawbody);
} catch (err) {
server.error(err);
context.send(400, "Bad data posted");
return;
}
if ("light" in data) {
// Does the 'light' value indicate a known command?
// If so, send an appropriate message to the device to issue a command
// to the lamp via Zigbee
if (data.light == "on") device.send("light.on", true);
if (data.light == "off") device.send("light.on", false);
if (data.light == "toggle") device.send("light.toggle", true);
// Reply to the source, ie. the web UI
context.send(200, "OK");
}
});
lampAPI.post("/actions", function(context) {
// Handle a POST request made to /actions - non-light lamp actions
local data;
try {
// Attempt to decode the received data as JSON
data = http.jsondecode(context.req.rawbody);
} catch (err) {
server.error(err);
context.send(400, "Bad data posted");
return;
}
if ("action" in data) {
// Does the 'action' value indicate toggling the debug state?
if (data.action == "debug") {
// In this case the data will also contain a 'debug' key with
// the chosen debug state as its value
lampState.debug = data.debug;
server.log("Debugging " + (lampState.debug ? "enabled" : "disabled"));
// Relay the choice to the device
device.send("set.debug", lampState.debug);
}
// Does the 'action' value indicate toggling the debug state
if (data.action == "identify") device.send("lamp.identify", true);
// Reply to the source, ie. the web UI
context.send(200, "OK");
}
});
// Zigbee Lamp Simulator - Gateway Device Code
// Copyright Electric Imp, Inc. 2018
// IMPORTS
#require "xbee.class.nut:1.0.0"
// CONSTANTS
// Zigbee Endpoints
const LAMP_ENDPOINT = 0x40;
const GATEWAY_ENDPOINT = 0x00;
// Zigbee Lamp Profile ID
const LAMP_PROFILE_ID = 0x0104;
// Lamp Cluster IDs
const LAMP_CLUSTER_BASIC = 0x0000;
const LAMP_CLUSTER_IDENTIFY = 0x0003;
const LAMP_CLUSTER_ONOFF = 0x0006;
// Commands
const LAMP_LIGHT_CMD_GETSTATE = 0x00;
const LAMP_LIGHT_CMD_ON = 0x00;
const LAMP_LIGHT_CMD_OFF = 0x01;
const LAMP_LIGHT_CMD_TOGGLE = 0x02;
const LAMP_COMMAND_IDENTIFY = 0x00;
const CLUSTER_BASIC_CMD_READ_ATTR = 0x00;
const CLUSTER_BASIC_CMD_READ_RSPD = 0x01;
const CLUSTER_BASIC_CMD_WRITE_ATTR = 0x02;
const CLUSTER_BASIC_CMD_WRITE_RSPD = 0x04;
const CLUSTER_IDENT_CMD_READ_ATTR = 0x00;
const CLUSTER_IDENT_CMD_READ_RSPD = 0x01;
const CLUSTER_ONOFF_CMD_READ_RSPD = 0x01;
// XBee Transmission Constants (Partial)
const SUCCESS = 0x00;
const FAILURE = 0x01;
const UNSUPPORTED_ATTRIBUTE = 0x86;
// ZCL Frame Structure Constants
const ZCL_FRAME_CONTROL_BYTE = 0x00;
const ZCL_TRANSACTION_ID_BYTE = 0x01;
const ZCL_COMMAND_ID_BYTE = 0x02;
// ZCL Data Type Constants
const UINT8 = 0x20;
const UINT16 = 0x21;
const ENUM8 = 0x30;
const CHAR_STRING = 0x42;
const BOOL = 0x10;
// GLOBALS
// The lamp's Zigbee addresses
local lampAddress64bit = "0013A20040DD310C";
local lampAddress16bit = 0xFFFE;
local coordinatorUART = null;
local coordinator = null;
local debug = true;
local trans = 1;
// Zigbee Cluster Library (ZCL) Attributes use by the application
local BasicAttributes = ["ZCL Version", "Application Version", "Stack Version", "Hardware Version",
"Manufacturer Name", "Model Identifier", "Production Date", "Power Source"];
local IdentAttributes = ["Identify Time"];
local LightAttributes = ["On/Off"];
// FUNCTIONS
// XBee Response Callback
// It is through this callback that all data received by the XBee
// is relayed to the host app
function xBeeResponse(error, response) {
if (error) {
server.error(error);
return;
}
// Does the frame contain an XBee-specific command?
if ("command" in response) {
if (response.command == "AI") {
// 'AI' is a frame of network status information
if ("data" in response) {
if (debug) server.log("Network Status: " + decodeAI(response.data[0]));
if (response.data[0] == 0x00) {
// Have successfully formed a network (signalled by a value == 0)
// so take no further action here
return;
}
}
// Network not yet up, so check again in 5 seconds
imp.wakeup(5, function() {
coordinator.sendLocalATCommand("AI");
});
}
if (response.command == "VR") {
local fv = (response.data[0] * 256) + response.data[1];
if (debug) server.log("XBee Firmware Version: " + getHex(fv, 4));
}
}
// 'cmdid' contains the XBee API packet ID that has been passed back to the host app
if ("cmdid" in response) {
// Is the packet an AT Command Response?
if (response.cmdid == 0x88) {
if (debug) server.log("Command " + response.command + " status: " + response.status.message);
}
// Is the packet a Modem Status?
if (response.cmdid == 0x8A) {
if (debug) server.log("Modem status: " + response.status.message);
}
// Is the packet an auto-generated Zigbee TX Response?
if (response.cmdid == 0x8B) {
if (debug) {
server.log("Delivery status : " + response.deliveryStatus.message);
server.log("Discovery status: " + response.discoveryStatus.message);
server.log("Dest. Addr: " + format("0x%04X", response.address16bit));
}
if (response.address16bit == 0xFFFD) {
// If the packet couldn't readch the target, 0xFFFD is returned as the
// the target 16-bit address, and we should set the recorded address to
// 'unknown' (0xFFFE)
lampAddress16bit = 0xFFFE;
if (debug) server.log("Destination Address not found - resetting stored address");
} else {
if (debug) server.log("Destination Address: " + getHex(response.address16bit, 4));
}
}
// Does the packet contain an explicit Zigbee RX packet?
if (response.cmdid == 0x91) {
// Is it a lamp-specific packet
if (response.profileID == LAMP_PROFILE_ID) {
// Update the lamp's 16-bit address (this can change)
lampAddress16bit = response.address16bit;
// Decode the ZCL frame
local zclFrame = response.data;
local frameControl = zclFrame[0];
local transaction = zclFrame[1];
local commandID = zclFrame[2];
if ((frameControl & 0x08) == 0) {
// Data flow direction is client (gateway) -> server (lamp), so ignore this packet
if (debug) server.log("Wrong direction: Client to Server");
return;
}
// Does the ZCL Frame target the Basic Cluster?
if (response.clusterID == LAMP_CLUSTER_BASIC) {
// Assume this is a global Cluster command (ie. the frame control Frame Type bits are 0x00)
// because these are the only commands we send (others are supported by the lamp)
if (commandID == CLUSTER_BASIC_CMD_READ_RSPD) {
// This is a Cluser-specific command: a response to a read request we have
// issued for lamp information
decodeAttributes(zclFrame, BasicAttributes);
}
return;
}
// Does the ZCL Frame target the Identify Cluster?
if (response.clusterID == LAMP_CLUSTER_IDENTIFY) {
// Assume this is a global Cluster command (ie. the frame control Frame Type bits are 0x00)
// because these are the only commands we send (others are supported by the lamp)
if (commandID == CLUSTER_IDENT_CMD_READ_RSPD) {
// Print out the requested attribute values
decodeAttributes(zclFrame, IdentAttributes);
}
}
// Does the ZCL Frame target the OnOFF Cluster?
if (response.clusterID == LAMP_CLUSTER_ONOFF) {
// Assume frame control Frame Type bits are 0x00, ie. a general command
if (commandID == CLUSTER_ONOFF_CMD_READ_RSPD) {
// Print out the requested attribute values
decodeAttributes(zclFrame, LightAttributes);
// Get lamp state attribute
// NOTE This is the only attribute we can read from the ONOFF cluster
// so we know it will be a frame bytes 6 (data type) and 7 (value)
// NOTE Value is 0x01 for true; 0x00 for false (Zigbe standard)
local dataType = zclFrame[6];
if (dataType == BOOL) {
local state = {};
state.on <- (zclFrame[7] == 0x01 ? true : false);
state.debug <- debug;
// Send the lamp state to the agent for use by the UI
agent.send("send.state", state);
}
}
}
} else if (response.profileID = 0x0000) {
// Update the lamp's 16-bit address (this can change)
lampAddress16bit = response.address16bit;
}
}
}
}
function getHex(value, digits) {
// Display 'value' as a hex number of 'digits' digits in length
local fs = digits == 4 ? "%04X" : "%02X";
return ("0x" + format(fs, value));
}
function decodeAttributes(frame, attList) {
// Extract the list of attributes and their records, decode and then display them
// NOTE These Attributes and their values are defined by the ZCL Specification
if (debug) {
server.log("Decoding ZCL Packet Payload...");
// Set i to the first data byte in the received ZCL frame
local i = 3;
// Iterate through the frame bytes to find Attribute Records:
// Attribute ID (16-bit, little endian)
// Read Operation Status (8-bit)
// Attribute Data Type (8-bit)
// Attribute Value (8 or more bits, according to type)
// NOTE For strings, the first byte of the value is the string length
do {
local attribute = frame[i] + (frame[i + 1] * 256);
local status = frame[i + 2];
if (status != 0x00) {
server.error("Bad attribute");
return;
} else {
local type = frame[i + 3];
// Handle out-of-sequence attributes
if (attribute == 0x4000) {
local length = frame[i + 4];
local s = "";
for (local j = 0 ; j < length ; j++) {
s = s + (frame[i + 5 + j]).tochar();
}
server.log("SW Build ID: " + s);
i = i + 5 + length;
} else {
if (type == BOOL) {
server.log(attList[attribute] + ": " + (frame[i + 4] == 0x01 ? "TRUE" : "FALSE"));
i = i + 5;
}
if (type == UINT8) {
server.log(attList[attribute] + ": " + getHex(frame[i + 4], 2));
i = i + 5;
}
if (type == UINT16) {
local a = frame[i + 4] + (frame[i + 5] * 256);
server.log(attList[attribute] + ": " + a + " seconds");
i = i + 6;
}
if (type == CHAR_STRING) {
local length = frame[i + 4];
local s = "";
for (local j = 0 ; j < length ; j++) {
s = s + (frame[i + 5 + j]).tochar();
}
server.log(attList[attribute] + ": " + s);
i = i + 5 + length;
}
}
}
} while (i < frame.len());
server.log("ZCL Packet Payload Decoded");
}
}
function decodeAI(code) {
// Decode the response from an "AI" command (network status) send to the XBee
// while the gateway is forming a Zigbee networl
if (code == 0x00) return "Successfully formed or joined a network";
local states = {};
states[0x21] <- "Scan found no PANs";
states[0x22] <- "Scan found no valid PANs based on current SC and ID settings";
states[0x23] <- "Valid Coordinator or Routers found, but they are not allowing joining";
states[0x24] <- "No joinable beacons found";
states[0x25] <- "Unexpected state, node should not be attempting to join at this time";
states[0x27] <- "Node Joining attempt failed (typically due to incompatible security settings)";
states[0x2A] <- "Coordinator Start attempt failed";
states[0x2B] <- "Checking for an existing coordinator";
states[0x2C] <- "Attempt to leave the network failed";
states[0xAB] <- "Attempted to join a device that did not respond";
states[0xAC] <- "Secure join error - network security key received unsecured";
states[0xAD] <- "Secure join error - network security key not received";
states[0xAF] <- "Secure join error - joining device does not have the right preconfigured link key";
states[0xFF] <- "Scanning for a Zigbee network (routers and end devices";
if (code in states) return states[code];
return "Unknown state";
}
function getBasicInfo() {
// This should cause the lamp to return some basic info about itself
local zFrame = blob(11);
// Make and add the ZCL header to the payload
zFrame.writeblob(makeZCLframeHeader());
// Add two Attribute IDs to the request
zFrame[3] = 0x04; // Attribute identifier LSB - Manufacturer's Name
zFrame[4] = 0x00; // Attribute identifier MSB
zFrame[5] = 0x05; // Attribute identifier LSB - Model ID
zFrame[6] = 0x00; // Attribute identifier MSB
zFrame[7] = 0x00; // Attribute identifier LSB - SW Version
zFrame[8] = 0x40; // Attribute identifier MSB
zFrame[9] = 0x06; // Attribute identifier LSB - Production Date
zFrame[10] = 0x00; // Attribute identifier MSB
// Send the request
send(LAMP_CLUSTER_BASIC, zFrame);
}
function getState() {
// Assemble a new ZCL packet that will be transmitted to the lamp
local zFrame = blob(5);
zFrame.writeblob(makeZCLframeHeader(true, false, true, trans, LAMP_LIGHT_CMD_GETSTATE));
zFrame[3] = 0x00; // An Attribute ID: in this case, the lamp state Attribute
zFrame[4] = 0x00;
// Send the ZCL packet to the ONOFF Cluster via Zigbee
send(LAMP_CLUSTER_ONOFF, zFrame);
}
function makeZCLframeHeader(isCmdGlobal = true, isCmdManufacturerSpecific = false, isDirectionToServer = true, transactionID = -1, cmdID = 0x00) {
// Paramters:
// 1. isCmdGlobal - Boolean: is the command global (true), or specific to the cluster (false)?
// 2. isCmdManufacturerSpecific - Boolean: is the command manufacturer specific (true), or not (false)?
// 3. isDirectionToServer - Boolean: is the command being sent to the cluster server (true)?
// NOTE In this application, the cluster server is the lamp
// 4. transactionID - An 8-bit value to identify the transaction.
// NOTE This can be used to match a response to a request, though we do not do so here
// 5. cmdID - An 8-bit value indicating the command being sent
// Assemble a ZCL frame header of three bytes
local header = blob(3);
// Assemble the Frame Control byte (non-set bits must be clear)
local fc = 0;
if (!isCmdGlobal) fc = fc + 1;
if (isCmdManufacturerSpecific) fc = fc | 4;
if (!isDirectionToServer) fc = fc | 8;
header[0] = fc;
// Add the transaction ID
if (transactionID == -1) {
header[1] = trans;
trans++;
if (trans > 255) trans = 0;
} else {
header[1] = transactionID;
}
// Add the command ID
header[2] = cmdID;
return header;
}
function send(clusterID, payload, frameID = -1) {
// Send the supplied ZCL packet out to the lamp via the XBee API
local r = coordinator.sendZCL(lampAddress64bit,
lampAddress16bit,
GATEWAY_ENDPOINT,
LAMP_ENDPOINT,
clusterID,
LAMP_PROFILE_ID,
payload,
0,
frameID);
}
function init() {
// Put coordinator into ZDO mode - this is necessary to
// receive ZDO commands and responses (ie. enables the explicit
// receive API frame (API ID 0x91) which indicates the source and
// destination endpoints, cluster ID, and profile ID)
coordinator.enterZDMode();
// Check the network status
coordinator.sendLocalATCommand("AI");
}
// RUNTIME START
// Instantiate Zigbee coordinator
// NOTE You will need to change this line depending on what imp dev board you are using
// and over which UART its XBee is connected. This code was written for the
// imp005 Breakout Board
coordinatorUART = hardware.uart1;
coordinator = XBee(coordinatorUART, xBeeResponse, true, true, false);
// Prep the coordinator
init();
// Request some basic data from the lamp
imp.wakeup(20, getBasicInfo);
// Set up agent message handlers
// These will be triggered by the agent as you operate the web-based UI
agent.on("light.on", function(state) {
// Switch the lamp on or off, as required, indicated by 'state', a boolean value
// First, assemble the ZCL packet that will be transmitted to the lamp
local command = state ? LAMP_LIGHT_CMD_ON : LAMP_LIGHT_CMD_OFF;
local zFrame = makeZCLframeHeader(false, false, true, trans, command);
// NOTE No payload required here (see 'lamp.identify', below)
// Send the ZCL packet to the ONOFF Cluster via Zigbee
send(LAMP_CLUSTER_ONOFF, zFrame);
});
agent.on("light.toggle", function(ignored) {
// Toggle the lamp on or off
// First, assemble the ZCL packet that will be transmitted to the lamp
local zFrame = makeZCLframeHeader(false, false, true, trans, LAMP_LIGHT_CMD_TOGGLE);
// NOTE No payload required here (see 'lamp.identify', below)
// Send the ZCL packet to the ONOFF Cluster via Zigbee
send(LAMP_CLUSTER_ONOFF, zFrame);
// Make sure we update the state information for the UI
// (because we don't store lamp state here)
getState();
});
agent.on("lamp.identify", function(state) {
// Make the lamp idenfify itself by flashing its light
// First, assemble the ZCL packet that will be transmitted to the lamp
local zFrame = blob(5);
zFrame.writeblob(makeZCLframeHeader(false, false, true, trans, LAMP_COMMAND_IDENTIFY));
// Here we need to pass a (little endian) 16-bit value to the lamp: how long (in seconds)
// that it will flash to identify itself (see ZCL spec 3.5.2.3)
zFrame[3] = 0x78; // Identity time - 120 seconds
zFrame[4] = 0x00;
// Send the ZCL packet to the IDENTIFY Cluster via Zigbee
send(LAMP_CLUSTER_IDENTIFY, zFrame);
});
agent.on("set.debug", function(state) {
// Record the debugging state if updated by the UI
debug = state;
});
agent.on("get.state", function(ignored) {
// This message is sent by the agent to get the lamp's actual state (rather
// than the infered one; see setDefaults() in the agent code) from the lamp
getState();
});
// Zigbee Lamp Simulator - Lamp Device Code
// Copyright Electric Imp, Inc. 2018
// IMPORTS
//#require "xbee.class.nut:1.0.0"
#import "~/documents/github/xbee/xbee.device.lib.nut"
// CONSTANTS
// Lamp Endpoints
const LAMP_ENDPOINT = 0x40;
const GATEWAY_ENDPOINT = 0x20;
// Smart Lamp Profile IDs
const LAMP_PROFILE_ID = 0x0104;
// Lamp Cluster IDs
const LAMP_CLUSTER_BASIC = 0x0000;
const LAMP_CLUSTER_IDENTIFY = 0x0003;
const LAMP_CLUSTER_ONOFF = 0x0006;
const LAMP_CLUSTER_LEVEL = 0x0008;
// Commands - Cluster Specific
const LAMP_BASIC_CMD_RESET = 0x00;
const LAMP_IDENT_CMD_IDENTIFY = 0x00;
const LAMP_IDENT_CMD_IDENTQRY = 0x01;
const LAMP_IDENT_CMD_IQRY_RSPNSE = 0x00;
const LAMP_ONOFF_CMD_ON = 0x00;
const LAMP_ONOFF_CMD_OFF = 0x01;
const LAMP_ONOFF_CMD_TOGGLE = 0x02;
const LAMP_LEVEL_MOVE_TO_LEVEL = 0x00;
const LAMP_LEVEL_MOVE = 0x01;
// Commands - General
const GLOBAL_READ_REQ = 0x00;
const GLOBAL_READ_RSP = 0x01;
const GLOBAL_WRITE_REQ = 0x02;
const GLOBAL_WRITE_RSP = 0x04;
// XBee Transmission Constants (Partial)
const SUCCESS = 0x00;
const FAILURE = 0x01;
const UNSUPPORTED_ATTRIBUTE = 0x86;
// XBee Frame Types
const XBEE_AT_CMD_STATUS = 0x88;
const XBEE_FRAME_MODEM_STATUS = 0x8A;
const XBEE_FRAME_ZIGBEE_TX_STATUS = 0x8B;
const XBEE_FRAME_ZIGBEE_EXP_RX = 0x91;
// ZCL Frame Structure Constants
const ZCL_FRAME_CONTROL_BYTE = 0x00;
const ZCL_TRANSACTION_ID_BYTE = 0x01;
const ZCL_COMMAND_ID_BYTE = 0x02;
// ZCL Data Type Constants
const UINT8 = 0x20;
const UINT16 = 0x21;
const ENUM8 = 0x30;
const CHAR_STRING = 0x42;
const BOOL = 0x10;
// GLOBALS
local gatewayAddress64bit = "0000000000000000"; // The coordinator
local gatewayAddress16bit = 0xFFFE;
local zigbee = null;
local lampState = null;
local lampLightPin = null;
local lampUART = null;
local identifyTimer = null;
local debug = true;
local trans = 1;
// FUNCTIONS
// XBee Response Callback
// It is through this callback that all data received by the XBee
// is relayed to the host app
function xBeeResponse(error, response) {
if (error) {
// Report any error encountered
server.error(error);
return;
}
if ("command" in response) {
if (response.command == "MY") {
local a = (response.data[0] << 8) + response.data[1];
if (debug) server.log("Local 16-bit address: " + getHex(a, 4));
// Relay address back to requester
send16BitAddress(a);
}
}
// 'cmdid' contains the XBee API packet ID that has been passed back to the host app
if ("cmdid" in response) {
// Is the packet an AT Command Response?
if (response.cmdid == XBEE_AT_CMD_STATUS) {
if (debug) server.log("Command (" + response.command + ") status: " + response.status.message);
}
// Is the packet a Modem Status?
if (response.cmdid == XBEE_FRAME_MODEM_STATUS) {
if (debug) server.log("Modem status: " + response.status.message);
}
// Is the packet an auto-generated Zigbee TX Response?
if (response.cmdid == XBEE_FRAME_ZIGBEE_TX_STATUS) {
local s = "Zigbee TX Report: Delivery " + response.deliveryStatus.message + ", " + response.discoveryStatus.message;
if (response.address16bit == 0xFFFD) {
// If the packet couldn't readch the target, 0xFFFD is returned as the
// the target 16-bit address, and we should set the recorded address to
// 'unknown' (0xFFFE)
gatewayAddress16bit = 0xFFFE;
if (debug) server.log(s + ", Destination Address not found");
} else {
if (debug) server.log(s + ", Destination Address " + getHex(response.address16bit, 4));
gatewayAddress16bit = response.address16bit;
}
}
// Does the packet contain an explicit Zigbee RX packet?
if (response.cmdid == XBEE_FRAME_ZIGBEE_EXP_RX) {
// Is the Zigbee Profile ID and Endpoint that of the demo lamp?
if (response.profileID == LAMP_PROFILE_ID) {
// Store the current 16-bit lamp address
gatewayAddress16bit = response.address16bit;
// Decode the Zigbee Cluster Library (ZCL) frame
local zFrame = response.data;
local frameControl = zFrame[ZCL_FRAME_CONTROL_BYTE];
local transaction = zFrame[ZCL_TRANSACTION_ID_BYTE];
local commandID = zFrame[ZCL_COMMAND_ID_BYTE];
if ((frameControl & 0x08) != 0) {
// Data flow direction is server (lamp) -> client (gateway), so ignore this packet
if (debug) server.log("Wrong direction: Lamp to Gateway");
return;
}
// Does the ZCL Frame target the Basic Cluster?
if (response.clusterID == LAMP_CLUSTER_BASIC) {
if (frameControl == 0x01) {
// This is a Cluser-specific command
if (commandID == LAMP_BASIC_CMD_RESET) {
// Factory Reset
// NOTE This is not implemented in this simulation
} else {
if (debug) server.log("Unsupported Cluster-specific command received");
}
} else {
// This is a Global Cluser command
if (commandID == GLOBAL_READ_REQ) {
// Read Attributes Command - ie. payload contains a list of attributes to read
// each of which is a 16-bit value placed in a simple list
// Get all the supplied attributes
local attributes = getAttributes(zFrame);
if (attributes.len() > 0) {
// Create the Read Attributes Response
local responseData = blob();
// Iterate through the list of attributes and prepare return data payloads for each
foreach (att in attributes) {
switch (att) {
case 0x0000: // ZCL Version
responseData.writen(0x00, 'b'); // Attribute ID LSB
responseData.writen(0x00, 'b'); // Attribute ID MSB
responseData.writen(SUCCESS, 'b'); // Status - Success
responseData.writen(UINT8, 'b'); // Data type
responseData.writen(0x02, 'b'); // The data
break;
case 0x0001: // Application Version
responseData.writen(0x01, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT8, 'b');
responseData.writen(0x00, 'b');
break;
case 0x0002: // Stack Version
responseData.writen(0x02, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT8, 'b');
responseData.writen(0x00, 'b');
break;
case 0x0003: // Hardware Version
responseData.writen(0x03, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT8, 'b');
responseData.writen(0x00, 'b');
break;
case 0x0004: // Manufacturer's name
responseData.writen(0x04, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(CHAR_STRING, 'b');
local s = "The Electric Imp Lighting Company";
responseData.writen(s.len(), 'b'); // String length
responseData.writestring(s)
break;
case 0x0005: // Model ID
responseData.writen(0x05, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(CHAR_STRING, 'b');
local s = "Zigbee Lamp Simulator";
responseData.writen(s.len(), 'b');
responseData.writestring(s)
break;
case 0x0006: // Date Code
responseData.writen(0x06, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(CHAR_STRING, 'b');
local s = "2018/09/30";
responseData.writen(s.len(), 'b');
responseData.writestring(s)
break;
case 0x0007: // Power Source
responseData.writen(0x07, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(ENUM8, 'b');
responseData.writen(0x01, 'b'); // Assume we're on mains power
break;
case 0x4000: // SW Build ID
responseData.writen(0x00, 'b');
responseData.writen(0x40, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(CHAR_STRING, 'b');
local s = imp.getsoftwareversion();
responseData.writen(s.len(), 'b');
responseData.writestring(s)
break;
case 0x4003: // Product 10NC
responseData.writen(0x03, 'b');
responseData.writen(0x40, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(CHAR_STRING, 'b');
local s = imp.getbootromversion();
responseData.writen(s.len(), 'b');
responseData.writestring(s)
break;
default:
// Don't recognise the attribute so send back a FAIL
responseData.writen((att & 0xFF), 'b');
responseData.writen(((att & 0xFF00) >> 8), 'b');
responseData.writen(UNSUPPORTED_ATTRIBUTE, 'b');
break;
}
}
if (responseData.len() > 0) {
local frame = makeZCLFrame(0x08, transaction, GLOBAL_READ_RSP, responseData);
sendZCLFrame(response, frame);
}
} else {
if (debug) server.log("No attributes recogized");
}
} else {
if (debug) server.log("Unsupported global Cluster command received");
}
}
}
// Does the ZCL Frame target the Identify Cluster?
if (response.clusterID == LAMP_CLUSTER_IDENTIFY) {
if (frameControl == 0x01) {
// Process Cluster-specific commands
if (commandID == LAMP_IDENT_CMD_IDENTIFY) {
// Identify Command
// This takes a time and flashes the lamp so an installer
// can distinguish it from other lamps of its kind
local newTime = 60;
try {
newTime = zFrame[3] + (zFrame[4] * 256);
} catch (e) {
if (debug) server.log("Bad Idenifty Time supplied - Defaulting to 60s");
}
if (newTime > 0) lampState.identifyTime = newTime;
if (debug) server.log("Lamp Identify Time Set To: " + lampState.identifyTime + " seconds");
// Flash the imp's LED for 'lampIdentifyTime' seconds
if (!lampState.isIdentifying) {
if (debug) server.log("Lamp Identification Cycle On");
lampState.isIdentifying = true;
lampState.flash = lampState.isOn;
identifyTimer = imp.wakeup(1.0, identifyLoop);
} else {
identifyComplete();
}
} else if (commandID == LAMP_IDENT_CMD_IDENTQRY) {
// Identify Query Command
// This is used to check if the lamp is currently identifying itself
if (lampState.isIdentifying) {
// ZCL return payload is the current indentification time as a uint16
local responseData = blob();
responseData.writen((lampState.identifyTime & 0xFF), 'b');
responseData.writen((lampState.identifyTime & 0xFF00) >> 8, 'b');
local frame = makeZCLFrame(0x08, transaction, LAMP_IDENT_CMD_IQRY_RSPNSE, responseData);
sendZCLFrame(response, frame);
}
// NOTE Spec says 'take no further action' if the lamp is NOT identifying itself
} else {
if (debug) server.log("Unsupported Cluster-specific command received");
}
} else {
// Process global Cluster commands
if (commandID == GLOBAL_READ_REQ) {
// Get all of the requested attributes by their 16-bit IDs - should be only one, 0x0000
local attributes = getAttributes(zFrame);
if (attributes.len() > 0) {
// Create the Read Attributes Response
local responseData = blob();
foreach (att in attributes) {
switch (att) {
case 0x0000: // IdentifyTime
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT16, 'b');
responseData.writen((lampState.identifyTime & 0xFF), 'b');
responseData.writen((lampState.identifyTime & 0xFF00) >> 8, 'b');
break;
default:
// Don't recognise the attribute so send back a FAIL
responseData.writen((att & 0xFF), 'b');
responseData.writen(((att & 0xFF00) >> 8), 'b');
responseData.writen(UNSUPPORTED_ATTRIBUTE, 'b');
break;
}
}
if (responseData.len() > 0) {
local frame = makeZCLFrame(0x08, transaction, GLOBAL_READ_RSP, responseData);
sendZCLFrame(response, frame);
}
} else {
if (debug) server.log("No attributes supplied");
}
} else if (commandID == GLOBAL_WRITE_REQ) {
// Get all of the written attributes by their 16-bit IDs - should be only one, 0x0000
local attributes = getAttributes(zFrame);
if (attributes.len() > 0) {
// Create the Written Attributes Response
local responseData = blob();
foreach (att in attributes) {
switch (att) {
case 0x0000: // IdentifyTime
// Write the attribute value to app storage
lampState.identifyTime = zFrame[i + 3] + (zFrame[i + 4] * 256);
if (lampState.identifyTime == 0 && lampState.isIdentifying) identifyComplete();
if (debug) server.log("IdentityTime set to " + getHex(lampState.IdentifyTime, 4));
// And build the response
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT16, 'b');
responseData.writen((lampState.identifyTime & 0xFF), 'b');
responseData.writen((lampState.identifyTime & 0xFF00) >> 8, 'b');
break;
default:
// Don't recognise the attribute so send back a FAIL
responseData.writen((att & 0xFF), 'b');
responseData.writen(((att & 0xFF00) >> 8), 'b');
responseData.writen(UNSUPPORTED_ATTRIBUTE, 'b');
break;
}
}
if (responseData.len() > 0) {
local frame = makeZCLFrame(0x08, transaction, GLOBAL_WRITE_RSP, responseData);
sendZCLFrame(response, frame);
}
} else {
if (debug) server.log("No attributes supplied");
}
} else {
if (debug) server.log("Unsupported global Cluster command received");
}
}
}
// Does the ZCL Frame target the OnOFF Cluster?
if (response.clusterID == LAMP_CLUSTER_ONOFF) {
if (frameControl == 0x01) {
// Process Cluster-specific commands
if (commandID == LAMP_ONOFF_CMD_ON) {
// Turn lamp on
lampState.isOn = true;
setLampLevel();
} else if (commandID == LAMP_ONOFF_CMD_OFF) {
// Turn lamp off
lampState.isOn = false;
lampLightPin.write(0.0);
} else if (commandID == LAMP_ONOFF_CMD_TOGGLE) {
// Toggle lamp
lampState.isOn = !lampState.isOn;
setLampLevel();
} else {
if (debug) server.log("Unsupported Cluster-specific command received");
}
} else {
// Process global Cluster commands
if (commandID == GLOBAL_READ_REQ) {
// Get all of the requested attributes by their 16-bit IDs - should be only one, 0x0000
local attributes = getAttributes(zFrame);
if (attributes.len() > 0) {
// Create the Read Attributes Response
local responseData = blob();
foreach (att in attributes) {
switch (att) {
case 0x0000: // On/Off State
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(BOOL, 'b');
responseData.writen((lampState.isOn ? 0x01 : 0x00), 'b'); // Set 1 for on, 0 for off
break;
default:
// Don't recognise the attribute so send back a FAIL
responseData.writen((att & 0xFF), 'b');
responseData.writen(((att & 0xFF00) >> 8), 'b');
responseData.writen(UNSUPPORTED_ATTRIBUTE, 'b');
break;
}
}
if (responseData.len() > 0) {
local frame = makeZCLFrame(0x08, transaction, GLOBAL_READ_RSP, responseData);
sendZCLFrame(response, frame);
}
} else {
if (debug) server.log("No attributes supplied");
}
} else {
if (debug) server.log("Unsupported global Cluster command received");
}
}
}
if (response.clusterID == LAMP_CLUSTER_LEVEL) {
if (frameControl == 0x01) {
// Cluster-specific commands
if (commandID == LAMP_LEVEL_MOVE_TO_LEVEL) {
local newLevel = zFrame[3];
local transTime = zFrame[4] + (zFrame[5] << 8);
server.log("Level move: " + lampState.level + " -> " + newLevel);
if (lampState.level != newLevel) {
lampState.onLevel = newLevel;
lampState.transTime = transTime == 0xFFFF ? lampstate.onOffTransTime : transTime;
lampState.deltaFunction = function() {
local delta = lampState.level - lampState.onLevel;
lampState.level = lampState.level - (math.abs(delta) == delta ? 1 : -1);
setLampLevel();
if (lampState.level != lampState.onLevel) {
imp.wakeup((10.0 / transTime.tofloat()), lampState.deltaFunction);
} else {
server.log("Done!");
}
}.bindenv(this);
lampState.deltaFunction();
}
}
if (commandID == LAMP_LEVEL_MOVE) {
local mode = zFrame[3];
local rate = zFrame[4];
local delta = mode == 0x00 ? 1 : -1;
server.log("Move: " + (mode == 0x00 ? "up" : "down") + " in " + rate + " steps/second");
lampState.transTime = rate * delta;
lampState.deltaFunction = function() {
lampState.level = lampState.level + lampState.transTime;
if (lampState.level > 0xFF) lampState.level = 0xFF;
if (lampState.level <= 0x00) {
lampState.level = 0x00;
lampState.isOn = false;
}
setLampLevel();
if (lampState.level > 0x00 && lampState.level < 0xFF) {
lampState.isOn = true;
imp.wakeup(1.0, lampState.deltaFunction);
} else {
server.log("Done!");
}
}.bindenv(this);
lampState.deltaFunction();
}
} else {
// Global commands
if (commandID == GLOBAL_READ_REQ) {
// Get all of the requested attributes by their 16-bit IDs - should be only one, 0x0000
local attributes = getAttributes(zFrame);
if (attributes.len() > 0) {
// Create the Read Attributes Response
local responseData = blob();
foreach (att in attributes) {
switch (att) {
case 0x0000: // Current Level
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT8, 'b');
responseData.writen(lampState.level, 'b');
break;
case 0x0010: // OnOffTransitionTime
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT16, 'b');
responseData.writen(lampState.onOffTransTime & 0xFF, 'b');
responseData.writen((lampState.onOffTransTime & 0xFF00) >> 8, 'b');
break;
case 0x0011: // OnLevel
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT8, 'b');
responseData.writen(lampState.onLevel, 'b');
break;
case 0x0012: // OnTransitionTime
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT16, 'b');
responseData.writen(lampState.onTransTime & 0xFF, 'b');
responseData.writen((lampState.onTransTime & 0xFF00) >> 8, 'b');
break;
case 0x00113: // OffTransitionTime
responseData.writen(0x00, 'b');
responseData.writen(0x00, 'b');
responseData.writen(SUCCESS, 'b');
responseData.writen(UINT16, 'b');
responseData.writen(lampState.offTransTime & 0xFF, 'b');
responseData.writen((lampState.offTransTime & 0xFF00) >> 8, 'b');
break;
default:
// Don't recognise the attribute so send back a FAIL
responseData.writen((att & 0xFF), 'b');
responseData.writen(((att & 0xFF00) >> 8), 'b');
responseData.writen(UNSUPPORTED_ATTRIBUTE, 'b');
break;
}
}
if (responseData.len() > 0) {
local frame = makeZCLFrame(0x08, transaction, GLOBAL_READ_RSP, responseData);
sendZCLFrame(response, frame);
}
} else {
if (debug) server.log("No attributes supplied");
}
} else {
if (debug) server.log("Unsupported global Cluster command received");
}
}
}
}
}
}
}
function getHex(value, digits) {
// Display 'value' as a hex number of 'digits' digits in length
local fs = digits == 4 ? "%04X" : "%02X";
return ("0x" + format(fs, value));
}
function getAttributes(frame) {
// Extract the sequence of attribute IDs from the ZCL frame and return them as an array
local atts = [];
for (local i = 3 ; i < frame.len() ; i = i + 2) {
try {
local att = frame[i + 1] * 256 + frame[i];
atts.append(att);
} catch (err) {
// NOP
}
}
return atts;
}
function makeZCLFrame(frameControl, transaction, command, response) {
// Build a ZCL response frame
local frame = blob(3 + response.len());
// Write in the header
frame[ZCL_FRAME_CONTROL_BYTE] = frameControl;
frame[ZCL_TRANSACTION_ID_BYTE] = transaction;
frame[ZCL_COMMAND_ID_BYTE] = command;
// Write in the data payload
frame.seek(3, 'b');
frame.writeblob(response);
return frame;
}
function sendZCLFrame(response, frame) {
// Send the ZCL frame back out on the Zigbee radio
local fid = "frameid" in response ? response.frameid : -1;
local r = zigbee.sendZCL(response.address64bit,
response.address16bit,
LAMP_ENDPOINT,
response.sourceEndpoint,
response.clusterID,
response.profileID,
frame,
fid);
}
function identifyLoop() {
// As per the ZCL spec, decrement the value of 'lampIdentifyTime' every second
// until we get to zero, then stop the identification signal
lampState.identifyTime--;
if (lampState.identifyTime <= 0) {
identifyComplete();
return;
}
// Turn the lamp on or off, alternately
lampState.flash = !lampState.flash;
lampLightPin.write(lampState.flash ? 1.0 : 0);
// Come back in one second's time
identifyTimer = imp.wakeup(1.0, identifyLoop);
}
function identifyComplete() {
// The lamp has stopped identifying itself, so do the clean-up here
imp.cancelwakeup(identifyTimer);
lampState.isIdentifying = false;
lampState.identifyTime = 0;
if (debug) server.log("Lamp Identification Cycle Off");
// Swith the lamp on or off as per its pre-identification mode state
setLampLevel();
}
function setLampLevel() {
local dutyCyle = lampState.level.tofloat() / 255.0;
lampLightPin.write(lampState.isOn ? dutyCyle : 0.0);
}
function setLampDefaults() {
lampState = {};
lampState.identifyTime <- 0x0000; // 60 seconds
lampState.isIdentifying <- false;
lampState.isOn <- true;
lampState.flash <- true;
lampState.level <- 0xFE;
lampState.onLevel <- 0xFE;
lampState.onOffTransTime <- 0x0001; // 10ms
lampState.onTransTime <- 0x0001; // 10ms
lampState.offTransTime <- 0x0001; // 10ms
lampState.transTime <- 0x0001; // 10ms
lampState.deltaFunction <- function() {};
}
// RUNTIME START
// Set up the lamp's initial state
setLampDefaults();
// Configure the hardware
// NOTE You will need to change these lines if you are not working with an imp003-based
// board. This code was written for the imp003 Breakout Board
lampLightPin = hardware.pinL;
local dutyCyle = lampState.level.tofloat() / 255.0;
lampLightPin.configure(PWM_OUT, 0.01, (lampState.isOn ? dutyCyle : 0.0));
lampUART = hardware.uartDM;
// Configure the radio object
// NOTE You will need to change this line if you are not working with the imp005-based
// breakout board
zigbee = XBee(lampUART, xBeeResponse, true, true, true);
// Put lamp into ZDO mode - this is necessary to
// receive ZDO and ZCL commands and responses (ie. enables the explicit
// receive API frame (API ID 0x91) which indicates the source and
// destination endpoints, cluster ID, and profile ID)
zigbee.enterZDMode();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment